超值一篇分享,Docker:從入門到實戰過程全記錄

csdn雲計算 發佈 2022-04-24T10:34:48.550694+00:00

想要真正理解Docker,就不得不從虛擬化技術的發展歷程說起。普遍認為虛擬化技術經歷了物理機時代、虛擬機時代,目前已經進入到了容器化時代。

作者 | 天元浪子

來源 | CSDN博客


和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%\servicing\Packages\*Hyper-V*.mum >hyper-v.txt
for /f %%i in ('findstr /i . hyper-v.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%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:db8::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., 92:d0:c6: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: <name|uid>[:<group|gid>])
      --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, 18:46:25) 
[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\n')
    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\n')
    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的含義是:

  1. 以python:3.8為基礎鏡像
  2. 容器啟動命令的工作目錄為/files,在運行鏡像時,需要我們把宿主機的某目錄掛載到容器的/files目錄
  3. 複製cat.py到容器的根目錄下
  4. 啟動時運行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, 18:46:25) 
[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

就可以使用了。

關鍵字: