作者?|?天元浪子
和Docker相關的概念
想要真正理解Docker,就不得不從虛擬化技術的發展歷程說起。普遍認為虛擬化技術經歷了物理機時代、虛擬機時代,目前已經進入到了容器化時代。可以說,Docker是虛擬化技術不斷發展的必然結果。
那么,什么是容器呢?容器和虛擬機有什么不同?Docker和容器又是什么關系呢?搞明白這幾個問題,Docker的概念就清晰了。
1.1 虛擬機和容器
借助于VMWare等軟件,可以在一臺計算機上創建多個虛擬機,每個虛擬機都擁有獨立的操作系統,可以各自獨立的運行程序。這種分身術雖然隔離度高(操作系統級),使用方便(類似物理機),但占用存儲資源多(GB級)、啟動速度慢(分鐘級)的缺點也是顯而易見的。
相較于虛擬機,容器(Container)是一種輕量型的虛擬化技術,它虛擬的是最簡運行環境(類似于沙盒)而非操作系統,啟動速度快(秒級)、占用存儲資源少(KB級或MB級),容器間隔離度為進程級。在一臺計算機上可以運行上千個容器,這是容器技術對虛擬機的碾壓式優勢。
1.2 容器、鏡像和Docker
Docker是一個開源的應用容器引擎,可以創建容器以及基于容器運行的程序。Docker可以讓開發者打包他們的應用和依賴包到一個輕量級、可移植的容器中,然后發布到任何流行的Linux機器上,也可以實現虛擬化。
聽起來很簡單,但是在Docker和容器之間,還隱藏著一個鏡像的概念,令初學者頗感困惑。本質上,Docker鏡像是一個特殊的文件系統,它提供容器運行時所需的程序、庫、資源、配置等文件。Docker鏡像類似于一個py文件,它需要Docker的運行時(類似于Python解釋器)運行。鏡像被運行時,即創建了一個鏡像的實例,一個實例就是一個容器。
1.3 Docker 和 k8s
作為容器引擎,Docker為容器化的應用程序提供了開放的標準,使得開發者可以用管理應用程序的方式來管理基礎架構,實現快速交付、測試和部署代碼。隨著容器的大量使用,又產生了如何協調、調度和管理容器的問題,Docker的容器編排應運而生。
k8s是Google開源的一個容器編排引擎,它支持自動化部署、大規模可伸縮、應用容器化管理,是一個開源的,用于管理云平臺中多個主機上的容器化的應用,k8s的目標是讓部署容器化的應用簡單并且高效,k8s提供了應用部署、規劃、更新、維護的一種機制。
Docker和k8sr都是以containerd(容器化標準)作為運行時,因此使用Docker創建的鏡像完全可以在k8s中無障礙的使用。
Docker的安裝
2.1 在ubuntu中安裝
在linux系統中安裝Docker非常簡單,官方為我們提供了一鍵安裝腳本。這個方法也適用于Debian或CentOS等發行版。
curl?-sSL?https://get.daocloud.io/docker?|?sh
安裝過程如果出現超時,不要灰心,多試幾次,總會成功的。安裝完成后,Docker只能被root用戶使用,可以使用下面的命令取消權限限制:
sudo?gpasswd?-a?<你的用戶名>?docker
然后,重啟docker服務:
sudo?service?docker?restart
最后,關閉當前的命令行,重新打開新的命令行就可以了。
順便提一下,如果在CentOS下安裝,可能會出現一堆類似于下面的錯誤:
問題?1:?problem?with?installed?package?podman-2.0.5-5.module_el8.3.0+512+b3b58dca.x86_64
??-?package?podman-2.0.5-5.module_el8.3.0+512+b3b58dca.x86_64?requires?runc?>=?1.0.0-57,?but?none?of?the?providers?can?be?installed ??-?package?podman-3.0.1-6.module_el8.4.0+781+acf4c33b.x86_64?requires?runc?>=?1.0.0-57,?but?none?of?the?providers?can?be?installed ??-?package?podman-3.0.1-7.module_el8.4.0+830+8027e1c4.x86_64?requires?runc?>=?1.0.0-57,?but?none?of?the?providers?can?be?installed ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?cannot?install?the?best?candidate?for?the?job ??-?package?runc-1.0.0-64.rc10.module_el8.4.0+522+66908d0c.x86_64?is?filtered?out?by?modular?filtering ??-?package?runc-1.0.0-65.rc10.module_el8.4.0+819+4afbd1d6.x86_64?is?filtered?out?by?modular?filtering ??-?package?runc-1.0.0-70.rc92.module_el8.4.0+786+4668b267.x86_64?is?filtered?out?by?modular?filtering ??-?package?runc-1.0.0-71.rc92.module_el8.4.0+833+9763146c.x86_64?is?filtered?out?by?modular?filtering ?問題?2:?package?podman-3.0.1-6.module_el8.4.0+781+acf4c33b.x86_64?requires?runc?>=?1.0.0-57,?but?none?of?the?providers?can?be?installed ??-?package?containerd.io-1.4.3-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.3-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.3-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?containerd.io-1.4.3-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?docker-ce-3:20.10.7-3.el8.x86_64?requires?containerd.io?>=?1.4.1,?but?none?of?the?providers?can?be?installed ??-?package?containerd.io-1.4.3-3.2.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.3-3.2.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.3-3.2.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?containerd.io-1.4.3-3.2.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?podman-catatonit-3.0.1-6.module_el8.4.0+781+acf4c33b.x86_64?requires?podman?=?3.0.1-6.module_el8.4.0+781+acf4c33b,?but?none?of?the?providers?can?be?installed ??-?problem?with?installed?package?podman-catatonit-2.0.5-5.module_el8.3.0+512+b3b58dca.x86_64 ??-?package?podman-catatonit-3.0.1-7.module_el8.4.0+830+8027e1c4.x86_64?requires?podman?=?3.0.1-7.module_el8.4.0+830+8027e1c4,?but?none?of?the?providers?can?be?installed ??-?package?podman-3.0.1-7.module_el8.4.0+830+8027e1c4.x86_64?requires?runc?>=?1.0.0-57,?but?none?of?the?providers?can?be?installed ??-?package?containerd.io-1.4.3-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.3-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.3-3.2.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.3-3.2.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.4-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.4-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-68.rc92.module_el8.3.0+475+c50ce30b.x86_64 ??-?cannot?install?the?best?candidate?for?the?job ??-?package?runc-1.0.0-64.rc10.module_el8.4.0+522+66908d0c.x86_64?is?filtered?out?by?modular?filtering ??-?package?runc-1.0.0-65.rc10.module_el8.4.0+819+4afbd1d6.x86_64?is?filtered?out?by?modular?filtering ??-?package?runc-1.0.0-70.rc92.module_el8.4.0+786+4668b267.x86_64?is?filtered?out?by?modular?filtering ??-?package?runc-1.0.0-71.rc92.module_el8.4.0+833+9763146c.x86_64?is?filtered?out?by?modular?filtering ??-?package?containerd.io-1.4.4-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.4-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.4-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?containerd.io-1.4.4-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-70.rc92.module_el8.4.0+673+eabfc99d.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?conflicts?with?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?containerd.io-1.4.6-3.1.el8.x86_64?obsoletes?runc?provided?by?runc-1.0.0-73.rc93.module_el8.4.0+830+8027e1c4.x86_64 ??-?package?podman-catatonit-2.0.5-5.module_el8.3.0+512+b3b58dca.x86_64?requires?podman?=?2.0.5-5.module_el8.3.0+512+b3b58dca,?but?none?of?the?providers?can?be?installed ??-?package?podman-2.0.5-5.module_el8.3.0+512+b3b58dca.x86_64?requires?runc?>=?1.0.0-57,?but?none?of?the?providers?can?be?installed
這是由于docker和Podman沖突造成的,需要先卸載Podman:
yum?erase?podman?buildah
2.2 在Win10中安裝
Docker的運行,依賴linux的環境,官方提供了Docker Desktop for Windows,但是它需要安裝Hyper-V,Hyper-V是微軟開發的虛擬機,類似于 VMWare 或 VirtualBox,僅適用于 Windows 10。這個虛擬機一旦啟用,QEMU、VirtualBox 或 VMWare Workstation 15 及以下版本將無法使用!如果你必須在電腦上使用其他虛擬機(例如開發 Android 應用必須使用的模擬器),請不要使用 Hyper-V!
我的電腦是win10家庭版,不能直接安裝hyper-v,需要將下面的命令保存到cmd文件中:
pushd?"%~dp0" dir?/b?%SystemRoot%servicingPackages*Hyper-V*.mum?>hyper-v.txt for?/f?%%i?in?('findstr?/i?.?hyper-v.txt?2^>nul')?do?dism?/online?/norestart?/add-package:"%SystemRoot%servicingPackages\%%i" del?hyper-v.txt Dism?/online?/enable-feature?/featurename:Microsoft-Hyper-V-All?/LimitAccess?/ALL
然后在cmd文件上點擊右鍵,選擇使用管理員運行。執行完畢后會重啟,在重啟的過程中進行安裝。
2.3 Hello world
docker服務啟動的情況下,運行下面的命令:
docker?run?ubuntu:20.04?/bin/echo?"Hello?world"
此命令的含義是:
docker run:運行docker鏡像命令
ubuntu:20.04:鏡像名稱為ubuntu版本號為20.04
/bin/echo “Hello world”:運行參數,此鏡像的參數含義為運行鏡像的echo命令顯示hello world
第一次運行時,因為本地沒有ubuntu:20.04鏡像,docker會自動從鏡像服務器下載。下載過程可能需要多試幾次,只要成功一次,以后執行就不再需要下載了。
docker官方還提供了一個hello-world鏡像,可以直接運行:
docker?run?hello-world
此命令省略了鏡像版本和運行參數,docker使用latest作為版本,即最新版本。
從hello world的例子中,也可以體驗到,docker實例的運行是非常快的。
Docker鏡像的使用
docker官方的鏡像庫比較慢,在進行鏡像操作之前,需要將鏡像源設置為國內的站點。
新建文件/etc/docker/daemon.json,輸入如下內容:
{ ????"registry-mirrors"?:?[ ????????"https://registry.docker-cn.com", ????????"https://docker.mirrors.ustc.edu.cn", ????????"http://hub-mirror.c.163.com", ????????"https://cr.console.aliyun.com/" ????] }
然后重啟docker的服務:
systemctl?restart?docker
3.1 列出本地所有鏡像
執行命令 docker images 可以查看
$?docker?images REPOSITORY??????????TAG?????????????????IMAGE?ID????????????CREATED?????????????SIZE ubuntu??????????????20.04???????????????f643c72bc252????????5?weeks?ago?????????72.9MB hello-world?????????latest??????????????bf756fb1ae65????????12?months?ago???????13.3kB
當前我本地只有剛才安裝的兩個鏡像。
3.2 從鏡像庫中查找鏡像
執行命令 docker search 鏡像名稱可以從docker鏡像庫中查找鏡像。
$?docker?search?python NAME?????????????????????????????DESCRIPTION?????????????????????????????????????STARS??????????????OFFICIAL??????????AUTOMATED python???????????????????????????Python?is?an?interpreted,?interactive,?objec…???5757????????????????[OK]???????????????? django???????????????????????????Django?is?a?free?web?application?framework,?…???1039????????????????[OK]???????????????? pypy?????????????????????????????PyPy?is?a?fast,?compliant?alternative?implem…???260?????????????????[OK]???????????????? joyzoursky/python-chromedriver???Python?with?Chromedriver,?for?running?automa…???57??????????????????????????????????????[OK] nikolaik/python-nodejs???????????Python?with?Node.js?????????????????????????????57??????????????????????????????????????[OK] arm32v7/python???????????????????Python?is?an?interpreted,?interactive,?objec…???53?????????????????????????????????????? circleci/python??????????????????Python?is?an?interpreted,?interactive,?objec…???42?????????????????????????????????????? centos/python-35-centos7?????????Platform?for?building?and?running?Python?3.5…???38?????????????????????????????????????? centos/python-36-centos7?????????Platform?for?building?and?running?Python?3.6…???30?????????????????????????????????????? hylang???????????????????????????Hy?is?a?Lisp?dialect?that?translates?express…???29??????????????????[OK]???????????????? arm64v8/python???????????????????Python?is?an?interpreted,?interactive,?objec…???24?????????????????????????????????????? revolutionsystems/python?????????Optimized?Python?Images?????????????????????????18?????????????????????????????????????? centos/python-27-centos7?????????Platform?for?building?and?running?Python?2.7…???17?????????????????????????????????????? bitnami/python???????????????????Bitnami?Python?Docker?Image?????????????????????10??????????????????????????????????????[OK] publicisworldwide/python-conda???Basic?Python?environments?with?Conda.???????????6???????????????????????????????????????[OK] d3fk/python_in_bottle????????????Simple?python:alpine?completed?by?Bottle+Req…???5???????????????????????????????????????[OK] dockershelf/python???????????????Repository?for?docker?images?of?Python.?Test…???5???????????????????????????????????????[OK] clearlinux/python????????????????Python?programming?interpreted?language?with…???4??????????????????????????????????????? i386/python??????????????????????Python?is?an?interpreted,?interactive,?objec…???3??????????????????????????????????????? ppc64le/python???????????????????Python?is?an?interpreted,?interactive,?objec…???2??????????????????????????????????????? centos/python-34-centos7?????????Platform?for?building?and?running?Python?3.4…???2??????????????????????????????????????? amd64/python?????????????????????Python?is?an?interpreted,?interactive,?objec…???1??????????????????????????????????????? ccitest/python???????????????????CircleCI?test?images?for?Python?????????????????0???????????????????????????????????????[OK] s390x/python?????????????????????Python?is?an?interpreted,?interactive,?objec…???0??????????????????????????????????????? saagie/python????????????????????Repo?for?python?jobs????????????????????????????0
最好選擇官方(OFFICIAL)的鏡像,這樣的鏡像最穩定一些。
3.3 下載新的鏡像
執行命令docker pull 鏡像名稱:版本號即可下載新的鏡像。
$?docker?pull?python:3.8 3.8:?Pulling?from?library/python 6c33745f49b4:?Pull?complete? ef072fc32a84:?Pull?complete? c0afb8e68e0b:?Pull?complete? d599c07d28e6:?Pull?complete? f2ecc74db11a:?Pull?complete? 26856d31ce86:?Pull?complete? 2cd68d824f12:?Pull?complete? 7ea1535f18c3:?Pull?complete? 2bef93d9a76e:?Pull?complete? Digest:?sha256:9079aa8582543494225d2b3a28fce526d9a6b06eb06ce2bac3eeee592fcfc49e Status:?Downloaded?newer?image?for?python:3.8 docker.io/library/python:3.8
鏡像下載后,就可以使用鏡像來創建容器了。
Docker容器的使用
4.1 啟動容器
執行命令docker run即可啟動容器,也就是創建某個鏡像的實例。docker run命令非常復雜,可以先執行一個docker run --help來查看幫助:
$?docker?run?--help Usage:??docker?run?[OPTIONS]?IMAGE?[COMMAND]?[ARG...] Run?a?command?in?a?new?container Options: ??????--add-host?list??????????????????Add?a?custom?host-to-IP?mapping?(host:ip) ??-a,?--attach?list????????????????????Attach?to?STDIN,?STDOUT?or?STDERR ??????--blkio-weight?uint16????????????Block?IO?(relative?weight),?between?10?and?1000,?or?0?to?disable?(default?0) ??????--blkio-weight-device?list???????Block?IO?weight?(relative?device?weight)?(default?[]) ??????--cap-add?list???????????????????Add?Linux?capabilities ??????--cap-drop?list??????????????????Drop?Linux?capabilities ??????--cgroup-parent?string???????????Optional?parent?cgroup?for?the?container ??????--cidfile?string?????????????????Write?the?container?ID?to?the?file ??????--cpu-period?int?????????????????Limit?CPU?CFS?(Completely?Fair?Scheduler)?period ??????--cpu-quota?int??????????????????Limit?CPU?CFS?(Completely?Fair?Scheduler)?quota ??????--cpu-rt-period?int??????????????Limit?CPU?real-time?period?in?microseconds ??????--cpu-rt-runtime?int?????????????Limit?CPU?real-time?runtime?in?microseconds ??-c,?--cpu-shares?int?????????????????CPU?shares?(relative?weight) ??????--cpus?decimal???????????????????Number?of?CPUs ??????--cpuset-cpus?string?????????????CPUs?in?which?to?allow?execution?(0-3,?0,1) ??????--cpuset-mems?string?????????????MEMs?in?which?to?allow?execution?(0-3,?0,1) ??-d,?--detach?????????????????????????Run?container?in?background?and?print?container?ID ??????--detach-keys?string?????????????Override?the?key?sequence?for?detaching?a?container ??????--device?list????????????????????Add?a?host?device?to?the?container ??????--device-cgroup-rule?list????????Add?a?rule?to?the?cgroup?allowed?devices?list ??????--device-read-bps?list???????????Limit?read?rate?(bytes?per?second)?from?a?device?(default?[]) ??????--device-read-iops?list??????????Limit?read?rate?(IO?per?second)?from?a?device?(default?[]) ??????--device-write-bps?list??????????Limit?write?rate?(bytes?per?second)?to?a?device?(default?[]) ??????--device-write-iops?list?????????Limit?write?rate?(IO?per?second)?to?a?device?(default?[]) ??????--disable-content-trust??????????Skip?image?verification?(default?true) ??????--dns?list???????????????????????Set?custom?DNS?servers ??????--dns-option?list????????????????Set?DNS?options ??????--dns-search?list????????????????Set?custom?DNS?search?domains ??????--domainname?string??????????????Container?NIS?domain?name ??????--entrypoint?string??????????????Overwrite?the?default?ENTRYPOINT?of?the?image ??-e,?--env?list???????????????????????Set?environment?variables ??????--env-file?list??????????????????Read?in?a?file?of?environment?variables ??????--expose?list????????????????????Expose?a?port?or?a?range?of?ports ??????--gpus?gpu-request???????????????GPU?devices?to?add?to?the?container?('all'?to?pass?all?GPUs) ??????--group-add?list?????????????????Add?additional?groups?to?join ??????--health-cmd?string??????????????Command?to?run?to?check?health ??????--health-interval?duration???????Time?between?running?the?check?(ms|s|m|h)?(default?0s) ??????--health-retries?int?????????????Consecutive?failures?needed?to?report?unhealthy ??????--health-start-period?duration???Start?period?for?the?container?to?initialize?before?starting?health-retries?countdown?(ms|s|m|h)?(default?0s) ??????--health-timeout?duration????????Maximum?time?to?allow?one?check?to?run?(ms|s|m|h)?(default?0s) ??????--help???????????????????????????Print?usage ??-h,?--hostname?string????????????????Container?host?name ??????--init???????????????????????????Run?an?init?inside?the?container?that?forwards?signals?and?reaps?processes ??-i,?--interactive????????????????????Keep?STDIN?open?even?if?not?attached ??????--ip?string??????????????????????IPv4?address?(e.g.,?172.30.100.104) ??????--ip6?string?????????????????????IPv6?address?(e.g.,?2001:33) ??????--ipc?string?????????????????????IPC?mode?to?use ??????--isolation?string???????????????Container?isolation?technology ??????--kernel-memory?bytes????????????Kernel?memory?limit ??-l,?--label?list?????????????????????Set?meta?data?on?a?container ??????--label-file?list????????????????Read?in?a?line?delimited?file?of?labels ??????--link?list??????????????????????Add?link?to?another?container ??????--link-local-ip?list?????????????Container?IPv4/IPv6?link-local?addresses ??????--log-driver?string??????????????Logging?driver?for?the?container ??????--log-opt?list???????????????????Log?driver?options ??????--mac-address?string?????????????Container?MAC?address?(e.g.,?92c6:0a:29:33) ??-m,?--memory?bytes???????????????????Memory?limit ??????--memory-reservation?bytes???????Memory?soft?limit ??????--memory-swap?bytes??????????????Swap?limit?equal?to?memory?plus?swap:?'-1'?to?enable?unlimited?swap ??????--memory-swappiness?int??????????Tune?container?memory?swappiness?(0?to?100)?(default?-1) ??????--mount?mount????????????????????Attach?a?filesystem?mount?to?the?container ??????--name?string????????????????????Assign?a?name?to?the?container ??????--network?network????????????????Connect?a?container?to?a?network ??????--network-alias?list?????????????Add?network-scoped?alias?for?the?container ??????--no-healthcheck?????????????????Disable?any?container-specified?HEALTHCHECK ??????--oom-kill-disable???????????????Disable?OOM?Killer ??????--oom-score-adj?int??????????????Tune?host's?OOM?preferences?(-1000?to?1000) ??????--pid?string?????????????????????PID?namespace?to?use ??????--pids-limit?int?????????????????Tune?container?pids?limit?(set?-1?for?unlimited) ??????--platform?string????????????????Set?platform?if?server?is?multi-platform?capable ??????--privileged?????????????????????Give?extended?privileges?to?this?container ??-p,?--publish?list???????????????????Publish?a?container's?port(s)?to?the?host ??-P,?--publish-all????????????????????Publish?all?exposed?ports?to?random?ports ??????--read-only??????????????????????Mount?the?container's?root?filesystem?as?read?only ??????--restart?string?????????????????Restart?policy?to?apply?when?a?container?exits?(default?"no") ??????--rm?????????????????????????????Automatically?remove?the?container?when?it?exits ??????--runtime?string?????????????????Runtime?to?use?for?this?container ??????--security-opt?list??????????????Security?Options ??????--shm-size?bytes?????????????????Size?of?/dev/shm ??????--sig-proxy??????????????????????Proxy?received?signals?to?the?process?(default?true) ??????--stop-signal?string?????????????Signal?to?stop?a?container?(default?"SIGTERM") ??????--stop-timeout?int???????????????Timeout?(in?seconds)?to?stop?a?container ??????--storage-opt?list???????????????Storage?driver?options?for?the?container ??????--sysctl?map?????????????????????Sysctl?options?(default?map[]) ??????--tmpfs?list?????????????????????Mount?a?tmpfs?directory ??-t,?--tty????????????????????????????Allocate?a?pseudo-TTY ??????--ulimit?ulimit??????????????????Ulimit?options?(default?[]) ??-u,?--user?string????????????????????Username?or?UID?(format:?[: ]) ??????--userns?string??????????????????User?namespace?to?use ??????--uts?string?????????????????????UTS?namespace?to?use ??-v,?--volume?list????????????????????Bind?mount?a?volume ??????--volume-driver?string???????????Optional?volume?driver?for?the?container ??????--volumes-from?list??????????????Mount?volumes?from?the?specified?container(s) ??-w,?--workdir?string?????????????????Working?directory?inside?the?container
比如我們要執行python的shell,需要添加-it參數,即:docker run -it python:3.8
$?docker?run?-it?python:3.8? Python?3.8.7?(default,?Dec?22?2020,?1825)? [GCC?8.3.0]?on?linux Type?"help",?"copyright",?"credits"?or?"license"?for?more?information. >>>?
4.2 將宿主機的文件掛載到容器
docker容器與宿主機是隔離的,要想讓容器內的程序能訪問宿主機上的文件,需要通過-v參數將宿主機的文件掛載到容器中。
比如我們在宿主機上有一個hello.py,可以打印hello,想要在python容器中執行,就需要進行掛載。-v后還需要接兩個參數,分別是宿主機的目錄和容器內的目錄,兩者使用:分隔,路徑必須都是絕對路徑。
我的hello.py保存在主目錄的/docker_test目錄中,將這個目錄掛載到容器的/docker_test目錄,然后在容器內執行python /docker_test/hello.py:
$?docker?run?-v?~/docker_test:/docker_test?python:3.8?python?/docker_test/hello.py
hello
4.3 容器的端口映射
我們修改一下hello.py,創建一個socket服務端,并監聽5000端口,當有客戶端連接時,打印客戶端的地址,先客戶端發送hello,然后關閉連接:
import?socket ip_port?=?('127.0.0.1',?5000) sk?=?socket.socket() sk.bind(ip_port) sk.listen(5) while?True: ????print('server?waiting...') ????conn,addr?=?sk.accept() ????print(addr) ????conn.sendall(b'hello ') ????conn.close()
在容器內執行:
docker?run?-v?~/docker_test:/docker_test?python:3.8?python?/docker_test/hello.py
接下來,嘗試用telnet命令連接,結果卻是失敗的。原因是,127.0.0.1是宿主機的ip地址,5000是容器的端口,這與我們的習慣稍微有些不同。事實上,docker的容器是非常輕量的,它并沒有自己的網絡,要想訪問容器的端口,需要進行端口映射,將容器的某端口映射到宿主機的端口,客戶端連接時,只要與宿主機的端口進行連接就可以了。
需要注意的是,上面的代碼創建的服務器,無論如何也不可能被客戶端連接,因為代碼中綁定了127.0.0.1的ip,在容器中運行時,需要綁定所有ip,即0.0.0.0。
import?socket ip_port?=?('0.0.0.0',?5000) sk?=?socket.socket() sk.bind(ip_port) sk.listen(5) while?True: ????print('server?waiting...') ????conn,addr?=?sk.accept() ????print(addr) ????conn.sendall(b'hello ') ????conn.close()
然后,再使用-p參數,-p還需要三個參數,即宿主機的ip地址、宿主機的端口、容器的端口,三者之間使用:分隔。一般的,可以將宿主機的ip地址省略,只寫宿主機的端口:容器的端口即可。
docker?run?-v?~/docker_test:/docker_test?-it?-p?5001:5000?python:3.8?python?/docker_test/hello.py
這樣,就將容器的5000端口映射到了宿主機的5001端口,使用:
telnet?127.0.0.1?5001
即可與容器中的服務器進行連接。
4.4 容器管理
上面的服務運行之后,可以使用docker ps命令,查看運行中的容器:
$?docker?ps CONTAINER?ID?????IMAGE???????????COMMAND??????????????????CREATED???????????STATUS?????????PORTS????????????????????NAMES ec4c86b8a163?????python:3.8??????"python?/docker_test…"???5?seconds?ago?????Up?4?seconds???0.0.0.0:5000->5000/tcp???eager_wilson
顯示的內容有下面幾列:
CONTAINER ID:容器ID
IMAGE:鏡像名稱和版本
COMMAND:執行的命令
CREATED:容器創建時間
STATUS:容器的狀態
PORTS:端口映射
NAMES:容器名
要想結束容器,可以使用docker kill 容器ID命令。
自制Docker鏡像
一般而言,當我們的程序開發完成后,會連同程序文件與運行環境一起制作成一個新的鏡像。
要制作鏡像,需要編寫Dockerfile。DockeFile由多個命令組成,常用的命令有:
FROM:基于某個鏡像來制作新的鏡像。格式為:FROM 鏡像名稱:鏡像版本。
COPY:從宿主機復制文件,支持?、*等通配符。格式為:COPY 源文件路徑 目標文件路徑。
ADD:從宿主機添加文件,格式與COPY相同,區別在于當文件為壓縮文件時,會解壓縮到目標路徑。
RUN:在創建新鏡像的過程中執行的shell命令。格式為:RUN shell命令行。注意,此shell命令將在容器內執行。
CMD:在容器實例中運行的命令,格式與RUN相同。注意,如果在docker run時指定了命令,將不會執行CMD的內容。
ENTRYPOINT:在容器實例中運行的命令,格式與CMD相同。注意,如果在docker run時指定了命令,該命令會以命令行參數的形式傳遞到ENTRYPOINT中。
ENV:在容器中創建環境變量,格式為:ENV 變量名值。
注意,Docker鏡像中有一個層的概念,每執行一個RUN命令,就會創建一個層,層過多會導致鏡像文件體積增大。盡量在RUN命令中使用&&連接多條shell命令,減少RUN命令的個數,可以有效減小鏡像文件的體積。
5.1 自制顯示文本文件內容鏡像
編寫cat.py,接收一個文件名,由python讀取文件并顯示文件的內容:
import?os import?sys input?=?sys.argv[1] with?open(input,?"r")?as?fp: ????print(fp.read())
這個例子比較簡單,縮寫Dockerfile如下:
FROM?python:3.8 WORKDIR?/files COPY?cat.py?/cat.py ENTRYPOINT?["python",?"/cat.py"]
這個Dockerfile的含義是:
以python:3.8為基礎鏡像
容器啟動命令的工作目錄為/files,在運行鏡像時,需要我們把宿主機的某目錄掛載到容器的/files目錄
復制cat.py到容器的根目錄下
啟動時運行python /cat.py命令
需要說明的是,ENTRYPOINT有兩種寫法:
ENTRYPOINT?python?/cat.py ENTRYPOINT?["python",?"/cat.py"]
這里采用第二種寫法,是因為我們要在外部給容器傳遞參數。執行命令編譯Docker鏡像:
docker?build?-t?cat:1.0?.
這個命令中,-t的含義是目標,即生成的鏡像名為hello,版本號為1.0,別忘了最后那個.,這叫到上下文路徑,是指 docker 在構建鏡像,有時候想要使用到本機的文件(比如復制),docker build 命令得知這個路徑后,會將路徑下的所有內容打包。
這樣,我們的第一個鏡像就制作完成了,使用下面的命令執行它:
docker?run?-it?-v?~/docker_test/cat/files:/files?cat:1.0?test.txt
即可看到~/docker_test/cat/files/test.txt的內容。
5.2 自制web服務器鏡像
我們使用tornado開發一個網站,而python的官方鏡像是沒有tornado庫的,這就需要在制作鏡像時進行安裝。
測試的ws.py如下:
import?tornado.httpserver import?tornado.ioloop import?tornado.options import?tornado.web from?tornado.options?import?define,?options define("port",?default=8000,?help="run?on?the?given?port",?type=int) class?IndexHandler(tornado.web.RequestHandler): ????def?get(self): ????????self.write("Hello?world") if?__name__?==?"__main__": ????tornado.options.parse_command_line() ????app?=?tornado.web.Application(handlers=[(r"/",?IndexHandler)]) ????http_server?=?tornado.httpserver.HTTPServer(app) ????http_server.listen(options.port) ????tornado.ioloop.IOLoop.instance().start()
編寫Dockerfile文件如下:
FROM?python:3.8 WORKDIR?/ws COPY?ws.py?/ws/ws.py RUN?pip?install?tornado CMD?python?hello.py
在此我們驗證一下CMD與ENTRYPOINT的區別。在Dockerfile所在有目錄下執行如下命令:
docker?build?-t?ws:1.0?.
執行完成后,再使用docker images使用就可以看到生成的鏡像了,然后使用下面的命令運行:
docker?run?-it?-p?8000:8000?ws:1.0
在瀏覽器中輸入宿主機的ip和8000端口,就可以看到頁面了。
在這個例子中,我使用的運行命令是CMD,如果在docker run中指定的其他的命令,此命令就不會被執行,如:
$?docker?run?-it?-p?8000:8000?ws:1.0?python Python?3.8.7?(default,?Dec?22?2020,?1825)? [GCC?8.3.0]?on?linux Type?"help",?"copyright",?"credits"?or?"license"?for?more?information. >>>?
此時,容器中被執行的是python命令,而不是我們的服務。在更多情況下,我們希望在docker run命令中為我們的服務傳參,而不是覆蓋執行命令,那么,我們應該使用ENTRYPOINT而不是CMD:
FROM?python:3.8 WORKDIR?/ws COPY?ws.py?/ws/ws.py RUN?pip?install?tornado ENTRYPOINT?python?ws.py
上面這種寫法,是不支持傳遞參數的,ENTRYPOINT和CMD還支持另一種寫法:
FROM?python:3.8 WORKDIR?/ws COPY?ws.py?/ws/ws.py RUN?pip?install?tornado ENTRYPOINT?["python",?"ws.py"]
使用這種寫法,docker run命令中的參數才可以傳遞給hello.py:
docker?run?-it?-p?8000:9000?ws:1.0?--port=9000
這個命令中,--port=9000被作為參數傳遞到hello.py中,因此容器內的端口就成了9000。
在生產環境中運行時,不會使用-it選項,而是使用-d選項,讓容器在后臺運行:
$?docker?run?-d?-p?8000:9000?ws:1.0?--port=9000 4a2df9b252e2aff6a8853b3a8bf46c0577545764831bb7557b836ddcd85cba70 $?docker?ps??????????????????????????????????????? CONTAINER?ID???IMAGE????????COMMAND??????????????????CREATED???????????STATUS????????????PORTS????????????????????NAMES 4a2df9b252e2???hello:1.0????"python?ws.py?--p…"???9?seconds?ago?????Up?8?seconds??????0.0.0.0:8000->9000/tcp???elegant_sammet
這種方式下,即使當前的控制臺被關閉,該容器也不會停止。
5.3 自制apscheduler服務鏡像
接下來,制作一個使用apscheduler編寫的服務鏡像,代碼如下:
import?sys import?shutil from?apscheduler.schedulers.blocking?import?BlockingScheduler from?apscheduler.triggers.cron?import?CronTrigger def?scan_files(): ????shutil.copytree(sys[1],?sys[2]) scheduler?=?BlockingScheduler() scheduler.add_job( ????scan_files, ????trigger=CronTrigger(minute="*"), ????misfire_grace_time=30 )
Dockerfile也是信手拈來:
FROM?python:3.8 WORKDIR?/ COPY?sch.py?/sch.py RUN?pip?install?apscheduler ENTRYPOINT?["python",?"sch.py"]
生成鏡像:
docker?build?-t?sch:1.0?.
應該可以運行了,文件復制需要兩個目錄,在運行時,可以使用兩次-v來掛載不同的目錄:
docker?run?-d?-v?~/docker_test/sch/src:/src?-v?~/docker_test/sch/dest:/dest?sch:1.0?/src?/dest
多階段構建壓縮鏡像體積
前面用到的官方python鏡像大小足足882MB,在這個基礎上,再安裝用到的第三方庫,添加項目需要的圖片等資源,大小很容易就超過1個G,這么大的鏡像,網絡傳給客戶非常的不方便,因此,減小鏡像的體積是非常必要的工作。
docker hub上有個一python:3.8-alpine鏡像,大小只有44.5MB。之所以小,是因為alpine是一個采用了busybox架構的操作系統,一般用于嵌入式應用。我嘗試使用這個鏡像,發現安裝一般的庫還好,但如果想安裝numpy等就會困難重重,甚至網上都找不到解決方案。
還是很回到基本的路線上來,主流的操作系統鏡像,ubuntu的大小為72.9MB,centos的大小為209MB——這也算是我更喜歡使用ubuntu的一個重要原因吧!使用ubuntu作為基礎鏡像,安裝python后的大小為139MB,再安裝pip后的大小一下子上升到了407MB,要是再安裝點其他東西,很容易就趕上或超過python官方鏡像的大小了。
看來,尋常路線是很難壓縮鏡像文件體積了。幸好,還有一條曲線救國的路可走,這就是多階段構建法。
多階段構建的思想其實很簡單,先構建一個大而全的鏡像,然后只把鏡像中有用的部分拿出來,放在一個新的鏡像里。在我們的場景下,pip只在構建鏡像的過程中需要,而對運行我們的程序卻一點用處也沒有。我們只需要安裝pip,再用pip安裝第三方庫,然后將第三方庫從這個鏡像中復制到一個只有python,沒有pip的鏡像中,這樣,pip占用的268MB空間就可以被節省出來了。
1、在ubuntu鏡像的基礎上安裝python:
FROM?ubuntu RUN?apt?update? ????&&?apt?install?python3
然后運行:
docker?build?-t?python:3.8-ubuntu?.
這樣,就生成了python:3.8-ubuntu鏡像。
2、在python:3.8-ubuntu的基礎上安裝pip:
FROM?python:3.8-ubuntu RUN?apt?install?python3
然后運行:
docker?build?-t?python:3.8-ubuntu-pip?.
這樣,就生成了python:3.8-ubuntu-pip鏡像。
3、多階段構建目標鏡像:
FROM?python:3.8-ubuntu-pip RUN?pip3?install?-i?https://pypi.tuna.tsinghua.edu.cn/simple?numpy FROM?python:3.8-ubuntu COPY?--from=0?/usr/local/lib/python3.8/dist-packages/?/usr/local/lib/python3.8/dist-packages/
這個dockerfile需要解釋一下了,因為它有兩個FROM命令。
第一個是以python:3.8-ubuntu-pip鏡像為基礎,安裝numpy,當然,在實際應用中,把所有用到的第三方庫出寫在這里。
第二個FROM是以FROM python:3.8-ubuntu鏡像為基礎,將第三方庫統統復制過來,COPY命令后的–from=0的意思是從第0階段進行復制。實際應用中再從上下文中復制程序代碼,添加需要的ENTRYPOINT等。
最后,再運行:
docker?build?-t?project:1.0?.
這然,用于我們項目的鏡像就做好了。比使用官方python鏡像構建的版本,小了大約750MB。
導入鏡像到生產環境
到此,我們的鏡像已經制作好了,可是,鏡像文件在哪,如何在生產環境下運行呢?
剛才使用docker images命令時,已經看到了生成的鏡像:
$?docker?images?????????????????????????? REPOSITORY??????????TAG?????????????????IMAGE?ID????????????CREATED?????????????SIZE hello???????????????1.0?????????????????01fe19111dc7????????59?minutes?ago??????893MB python??????????????3.8?????????????????f5041c8ae6b1????????13?days?ago?????????884MB ubuntu??????????????20.04???????????????f643c72bc252????????5?weeks?ago?????????72.9MB hello-world?????????latest??????????????bf756fb1ae65????????12?months?ago???????13.3kB
我們可以使用docker save命令將鏡像保存到指定的文件中,保存的文件是一個.tar格式的壓縮文件:
docker?save?-o?hello.tar?hello:1.0
將hello.tar復制到生產環境的機器上,然后執行導入命令:
docker?load?-i?hello.tar
就可以使用了。
編輯:黃飛
評論
查看更多