在 Docker 容器里面使用 docker run
/docker build
?
Docker 容器技術目前是微服務/持續集成/持續交付領域的第一選擇。而在 DevOps 中,我們需要將各種后端/前端的測試/構建環境打包成 Docker 鏡像,然后在需要的時候,Jenkins 會使用這些鏡像啟動容器以執行 Jenkins 任務。
為了方便維護,我們的 CI 系統如 Jenkins,也會使用 Docker 方式部署。
Jenkins 任務中有些任務需要將微服務構建成 Docker 鏡像,然后推送到 Harbor 私有倉庫中。
或者我們所有的 Jenkins Master 鏡像和 Jenkins Slave 鏡像本身都不包含任何額外的構建環境,執行任務時都需要啟動包含對應環境的鏡像來執行任務。
我們的 Jenkins Master、Jenkins Slaves 都是跑在容器里面的,該如何在這些容器里面調用 docker run
命令啟動包含 CI 環境的鏡像呢?
在這些 CI 鏡像里面,我們從源碼編譯完成后,又如何通過 docker build
將編譯結果打包成 Docker 鏡像,然后推送到內網倉庫呢?
答案下面揭曉。
一、原理說明:/var/run/docker.sock
Docker 采取的是 Client/Server 架構,我們常用的 docker xxx
命令工具,只是 docker 的 client,我們通過該命令行執行命令時,實際上是在通過 client 與 docker engine 通信。
我們通過 apt/yum 安裝 docker-ce 時,會自動生成一個 systemd 的 service,所以安裝完成后,需要通過 sudo systemctl enable docker.service
來啟用該服務。
這個 Docker 服務啟動的,就是 docker engine,查看 /usr/lib/systemd/system/docker.service
,能看到有這樣一條語句:
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
默認情況下,Docker守護進程會生成一個 socket(/var/run/docker.sock
)文件來進行本地進程通信,因此只能在本地使用 docker 客戶端或者使用 Docker API 進行操作。
sock 文件是 UNIX 域套接字,它可以通過文件系統(而非網絡地址)進行尋址和訪問。
因此只要以數據卷的形式將 docker 客戶端和上述 socket 套接字掛載到容器內部,就能實現 "Docker in Docker",在容器內使用 docker 命令了。具體的命令見后面的「示例」部分。
要記住的是,真正執行我們的 docker 命令的是 docker engine,而這個 engine 跑在宿主機上。所以這並不是真正的 "Docker in Docker".
二、示例
在容器內部使用宿主機的 docker,方法有二:
- 命令行方式:將
/usr/bin/docker
映射進容器內部,然后直接在容器內部使用這個命令行工具docker
- 需要的時候,也可以將
/etc/docker
文件夾映射到容器內,這樣容器內的docker
命令行工具也會使用與宿主機同樣的配置。
- 需要的時候,也可以將
- 編程方式:在容器內部以編程的方式使用 docker
- 通過 python 使用 docker: 在 Dockerfile 中通過
pip install docker
將 docker client 安裝到鏡像中來使用
- 通過 python 使用 docker: 在 Dockerfile 中通過
容器的啟動方式也有兩種,如下:
1. 直接通過 docker 命令啟動
示例命令如下:
docker run --name <name> \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
--user root \
<image-name>:<tag>
必須以 root 用戶啟動!(或者其他有權限讀寫 /var/run/docker.sock
的用戶)然后,在容器內就能正常使用 docker 命令,或者訪問宿主機的 docker api 了。
2. 使用 docker-compose 啟動
docker-compose.yml 文件內容如下:
version: '3.3'
services:
jenkins-master:
image: jenkinsci/blueocean:latest
container_name: jenkins-master
environment:
- TZ=Asia/Shanghai # 時區
ports:
- "8080:8080"
- "50000:50000"
volumes:
- ./jenkins_home:/var/jenkins_home # 將容器中的數據映射到宿主機
- /usr/bin/docker:/usr/bin/docker # 為容器內部提供 docker 命令行工具(這個隨意)
- /var/run/docker.sock:/var/run/docker.sock # 容器內部通過 unix socket 使用宿主機 docker engine
user: root # 必須確保容器以 root 用戶啟動!(這樣它才有權限讀寫 docker.socket)
restart: always
然后通過 docker-compose up -d
即可后台啟動容器。
Docker 中的 uid 與 gid
通過上面的操作,我們在容器內執行 docker ps
時,還是很可能會遇到一個問題:權限問題。
如果你容器的默認用戶是 root,那么你不會遇到這個問題,因為 /var/run/docker.sock
的 owner 就是 root.
但是一般來說,為了限制用戶的權限,容器的默認用戶一般都是 uid 和 gid 都是 1000 的普通用戶。這樣我們就沒有權限訪問 /var/run/docker.sock
了。
解決辦法:
方法一(不一定有效):在構建鏡像時,最后一層添加如下內容:
# docker 用戶組的 id,通常都是 999
RUN groupadd -g 999 docker \
&& usermod -aG docker <your_user_name>
這樣我們的默認用戶,就能使用 docker 命令了。
P.S.
999
不一定是 docker 用戶組,所以上述方法某些情況下可能失效。這時還是老老實實通過docker run -u root
啟動容器吧。(或者在docker-compose.yml
中添加user: root
屬性)