在介紹 Docker 具體的操作前,先簡要復習下 Docker 的架構,這樣可以更好地幫助我們理解 Docker 中的各個命令。
首先我們一直對 Docker 這個叫法就有些誤解,Docker 其實指代的是用於開發,部署,運行應用的一個平台。平常中說的 Docker 准確來說是 Docker Engine.
Docker Engine 是一個 C/S 架構的應用。其中主要的組件有:
- Docker Server:長時間運行在后台的程序,就是熟悉的 daemon 進程.
- Docker Client: 命令行接口的客戶端。
- REST API: 用於和 daemon 進程的交互。
我們通過給 Docker Client 下發各種指令,然后 Client 通過 Docker daemon 提供的 REST API 接口進行交互,來讓 daemon 處理編譯,運行,部署容器的繁重工作。 大多數情況下, Docker Client 和 Docker Daemon 運行在同一個系統下,但有時也可以使用 Docker Client 來連接遠程的 Docker Daemon 進程,也就是遠程的 Server 端。
清楚了 Docker 的簡單架構,就可以了解下具體的命令了。
Docker 鏡像構建指令
指令1:構建鏡像
docker build [OPTIONS] PATH | URL | -
Docker 構建鏡像的上下文。
這里的上下文指的就是命令的最后一個參數 PATH | URL| -
,具體來說就 docker build .
中的 .
很多人以為這個 .
是 DockerFile 的位置,其實不然,准確來說是構建上下文的位置。前面說到 Docker 是 C/S 架構,在 Client 端下發具體的命令,在 Server 端(Daemon)執行具體的內容。這也就意味着,構建鏡像的過程其實是在 Server 端完成的。而上下文的出現,就是為了把需要的內容傳遞給 Server,這也就為什么在每次構建時都能看到這樣一句話。
[root@localhost python_shell]# docker build --rm -t temp/python-test .
Sending build context to Docker daemon 4.608kB
這里的 Sending 其實就是把本地 Client 端的文件內容,拷貝到 Server 端。而許多初學者,在 DockerFile 中寫出了 COPY /opt/xxxx /app
這樣的話,其實就是沒有理解上下文的概念,並不知道在 Server 端是沒有 opt/xxxx
的文件的。
還有的人將 Docker File 放在硬盤根目錄執行,殊不知,這樣會將根目錄所有的文件都拷貝到 Server 端,造成構建極其緩慢。
Options 常用參數:
-t
: 打包出鏡像的名稱及標簽,通常寫法為name:tag
--rm
: 構建成功后,刪除中間產生的容器。--force-rm=true
: 無論是否構建成功,都刪除中間產生的容器--no-cache
: 構建鏡像時不使用緩存。-f
: 指定 DockerFile 的路徑
docker build --force-rm --no-cache -t local/centos7:v1 .
DockerFile 指令及編寫規范
指令1:指定基礎鏡像
通過 FROM
來制定基礎鏡像,命令很簡單,但有一點需要注意的是,一定確切指定基礎鏡像的版本,而不是寫成 latesst
, 因為隨着時間推移,官方的最新鏡像都會一直更新,這樣就會造成無法構建的情況。
FROM centos:7 # That's perfect!
FROM centos:latest # That's so bad!
指令2:容器中執行命令
RUN 命令用於在容器中執行命令行的命令。一般有兩種寫法:
- shell 形式:以這種模式執行時,在容器內部是以
/bin/sh -c "task command"
執行,所以 1 號進程是 bash 進程。
RUN 后面直接跟 shell 命令就可以了。切記,在 shell 形式下,不要把命令拆成多行 RUN。因為每一次的 RUN 都會構建一層新的鏡像,保存了很多沒有用的運行信息。而且 Union FS 是由最大層數限制的。所以盡量將命令合成一行。
RUN yum -y install httpd; yum clean all; systemctl enable httpd.service # That's perfect!
RUM yum -y install httpd / # Another perfect solution!
yum clean all /
systemctl enable httpd.service
RUN yum -y install httpd # That's so bad!
RUN yum clean all;
....
還有一點需要注意,把構建時沒用的依賴包想着清空。否則的話,隨着鏡像的重復構建,保存了大量的沒有信息。
- exec 寫法
exec 寫法更像函數調用中的格式。使用該模式時,進行的命令就是容器內的 1 號進程。
RUN ["可執行文件", "參數1", "參數2"]
RUN ["yum", "-y", "install", "httpd"]
RUN:在執行命令時會新創建一層,通常用於安裝軟件包
CMD:用於設置默認的執行命令,可被 container 運行時替換。如果在 DockerFile 中寫入多條 CMD 命令,僅最后一條有意義。
ENTRYPOINT:命令被執行時,是不可被忽略的。一般用於啟動時的命令。
指令3:設置工作目錄
WORKDIR 用於改變各層的工作目錄(也就是進入容器內的默認目錄),如果指定的目錄不存在就會創建它。工作目錄在構建過程中,可以被各層都訪問到。
WORKDIR /src
指令4:設置匿名卷
在容器運行時,盡量對容器的存儲層不進行寫操作,對於像數據庫中這樣動態的數據文件應該用 VOLUME 來保存。而在 DockerFile VOLUME 可以將目錄指定為匿名卷。這樣在運行時,如果沒有掛載指定的目錄,並不會像容器的存儲層寫入數據,保證存儲層的無狀態化。
VOLUME /data
開啟 Systemd Centos7 鏡像
官方 Centos7 的鏡像已經包含了 systemd 的功能,只是沒有開啟。這里只需要以其為基礎鏡像,打開 systemd 的功能即可。需要注意的是,打開 systemd 需要在運行時開啟特權以掛載 Cgroup 等內容。
編寫 DockerFile
[root@localhost docker_images]# cat Dockerfile
FROM centos:7
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
- VOLUME 指定了匿名目錄,不會在存儲層保存該目錄的內容,適用於動態變化等持久性文件。
- CMD 保證容器啟動時開啟 systemd
打包鏡像
docker build --rm -t local/c7-systemd .
- --rm: 表示刪除打包時臨時的容器
運行鏡像
docker run --privileged=true -ti -v /sys/fs/cgroup:/sys/fs/cgroup:ro local/c7-systemd
- --privileged: 給予容器特殊的權限,來掛載 Cgroup 等。
Httpd 鏡像
下面基於上面開啟 systemd 的鏡像為基礎,打包 Httpd 鏡像。
編寫 DockerFile
FROM local/c7-systemd
RUN yum -y install httpd; yum clean all; systemctl enable httpd.service
EXPOSE 80
- 下載依賴包后,刪除沒有用的內容,是編寫 DockerFile 好習慣。
編譯 DockerFile
docker build --rm -t local/c7-systemd-httpd .
運行 Container
docker run --privileged=true -ti -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
-p 80:80 local/c7-systemd-httpd
在下載依賴時出現網絡問題,請查看 Docker 代理 這篇文章。
Python 鏡像
有時我們需要在容器中運行 python 腳本,下面來打包類似的鏡像。在下載需要的依賴時,通常的服務器並沒訪問公網的能力,這時需要為容器配置配置代理,在下載依賴后,有時由於代理的原因,導致內部的服務器無法訪問,這時可以再將設置的代理清空。
創建文件
注意,這里的文件要和 DockerFile 在同級目錄下。
# 創建 requirements 文件保存依賴
[root@localhost home]# cat python_shell/requirements.txt
requests==2.21.0
# 編寫 Python 腳本
[root@localhost home]# cat python_shell/success.py
print("python Running", "!");
編寫 DockerFile
FROM python:3.6.8
# set proxy
ENV MY_PROXY_URL="http://173.39.112.117:80"
ENV HTTP_PROXY=$MY_PROXY_URL \
HTTPS_PROXY=$MY_PROXY_URL \
FTP_PROXY=$MY_PROXY_URL \
http_proxy=$MY_PROXY_URL \
https_proxy=$MY_PROXY_URL \
ftp_proxy=$MY_PROXY_URL
WORKDIR /src
COPY . .
RUN ["pip", "install", "--no-cache-dir", "-r", "./requirements.txt"]
# clear the proxy
ENV MY_PROXY_URL=
ENV HTTP_PROXY=$MY_PROXY_URL \
HTTPS_PROXY=$MY_PROXY_URL \
FTP_PROXY=$MY_PROXY_URL \
http_proxy=$MY_PROXY_URL \
https_proxy=$MY_PROXY_URL \
ftp_proxy=$MY_PROXY_URL
CMD ["python", "./success.py"]
打包鏡像
docker build --rm -t temp/python-test .
運行鏡像
[root@localhost home]# docker run temp/python-test
python Running !
參考
https://blog.fundebug.com/2017/05/15/write-excellent-dockerfile/