Docker學習手冊(1)-基礎篇


img

1. Docker 技術背景

Docker 是基於Go 語言實現的開源容器項目,誕生於2013 年年初,最初發起者是 dotCloud 公司。Docker 自開源后受到廣泛的關注和l 討論,目前已有多個相關項目(包括 Docker 三劍客、Kubemetes 等),逐漸形成了圍繞Docker 容器的生態體系。 由於Docker 在業界造成的影響力實在太大, dotCloud 公司后來也直接改名為Docker Inc ,並專注於Docker 相關技術和產品的開發。

2. Docker 容器

容器其實是一種沙盒技術。顧名思義,沙盒就是能夠像一個集裝箱一樣,把你的應用“裝”起來的技術。這樣,應用與應用之間,就因為有了邊界而不至於相互干擾;而被裝進集裝箱的應用,也可以被方便地搬來搬去,這就是 PaaS 服務最理想的狀態。

在講容器“邊界”之前,我們講講計算機程序是怎么運行的。假如現在要寫一個加法運算的小程序,這個程序需要的輸入來自於一個文件,計算結果輸出到另一個文件。

由於計算機只認識 0 和 1,所以無論用哪種語言編寫這段代碼,最后都需要通過某種方式翻譯成二進制文件,才能在計算機操作系統中運行起來。

而為了能夠讓這些代碼正常運行,我們往往還要給它提供數據,比如我們這個加法程序所需要的輸入文件。這些數據加上代碼本身的二進制文件,放在磁盤上,就是我們平常所說的一個“程序”,也叫代碼的可執行鏡像(executable image)。

然后,我們就可以在計算機上運行這個“程序”了。

首先,操作系統從“程序”中發現輸入數據保存在一個文件中,所以這些數據就會被加載到內存中待命。同時,操作系統又讀取到了計算加法的指令,這時,它就需要指示 CPU 完成加法操作。而 CPU 與內存協作進行加法計算,又會使用寄存器存放數值、內存堆棧保存執行的命令和變量。同時,計算機里還有被打開的文件,以及各種各樣的 I/O 設備在不斷地調用中修改自己的狀態。

就這樣,一旦“程序”被執行起來,它就從磁盤上的二進制文件,變成了計算機內存中的數據、寄存器里的值、堆棧中的指令、被打開的文件,以及各種設備的狀態信息的一個集合。像這樣一個程序運行起來后的計算機執行環境的總和,就是我們今天的主角:進程。

所以,對於進程來說,它的靜態表現就是程序,平常都安安靜靜地待在磁盤上;而一旦運行起來,它就變成了計算機里的數據和狀態的總和,這就是它的動態表現。

容器技術的核心功能,就是通過約束和修改進程的動態表現,從而為其創造出一個“邊界”。

img

對於 Docker 等大多數 Linux 容器來說,Cgroups 技術是用來制造約束的主要手段,而 Namespace 技術則是用來修改進程視圖的主要方法。

而除了 PID Namespace,Linux 操作系統還提供了 Mount、UTS、IPC、Network 和 User 這些 Namespace,用來對各種不同的進程上下文進行“障眼法”操作。比如,Mount Namespace,用於讓被隔離進程只看到當前 Namespace 里的掛載點信息;Network Namespace,用於讓被隔離進程看到當前 Namespace 里的網絡設備和配置。這,就是 Linux 容器最基本的實現原理了。

所以,Docker 容器這個聽起來玄而又玄的概念,實際上是在創建容器進程時,指定了這個進程所需要啟用的一組 Namespace 參數。這樣,容器就只能“看”到當前 Namespace 所限定的資源、文件、設備、狀態,或者配置。而對於宿主機以及其他不相關的程序,它就完全看不到了。所以說,容器,其實是一種特殊的進程而已。

3. Docker 架構

Docker Architecture Diagram

Docker 使用客戶端/服務器端架構。 Docker 客戶端與 Docker 守護進程對話,后者負責構建、運行和分發 Docker 容器的繁重工作。 Docker 客戶端和守護程序可以在同一系統上運行,或者可以將 Docker 客戶端連接到遠程 Docker 守護程序。 Docker 客戶端和守護進程使用 REST API、UNIX 套接字或網絡接口進行通信。 另一個 Docker 客戶端是 Docker Compose,它允許您使用由一組容器組成的應用程序。

4. Docker 組成

Docker主機(Host):一個物理機或虛擬機,用於運行Docker服務進程和容器。

Docker服務端(Server): Docker守護進程,運行docker容器。

Docker客戶端(Client):客戶端使用docker命令或其他工具調用docker APl。

Docker倉庫(Registry):保存鏡像的倉庫,類似於git或 svn 這樣的版本控制系

Docker鏡像(Images):鏡像可以理解為創建實例使用的模板。

Docker容器(Container):容器是從鏡像生成對外提供服務的一個或一組服務。

官方倉庫: https://hub.docker.com/

5. Docker 安裝

登錄阿里雲鏡像站

阿里巴巴開源鏡像站-OPSX鏡像站-阿里雲開發者社區 (aliyun.com)

image-20210925164259243

安裝Docker社區版

# step 1: 安裝必要的一些系統工具
sudo apt update
sudo apt -y install apt-transport-https ca-certificates curl software-properties-common
# step 2: 安裝GPG證書
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 寫入軟件源信息
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# Step 4: 更新並安裝Docker-CE
sudo apt -y update
# 安裝指定版本的Docker-CE:
# Step 1: 查找Docker-CE的版本:
root@localhost:~# apt-cache madison docker-ce
 docker-ce | 5:20.10.8~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.7~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.6~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.5~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.4~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.3~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.2~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.1~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.0~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.15~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.14~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.13~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.12~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.11~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.10~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.9~3-0~ubuntu-focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages
# Step 2: 安裝指定版本的Docker-CE:
root@localhost:~# apt install -y docker-ce=5:19.03.15~3-0~ubuntu-focal

deamon配置文件優化,編輯配置文件:/etc/docker/daemon.json

root@localhost:~# cat /etc/docker/daemon.json
{
  "registry-mirrors": ["https://0g7sflox.mirror.aliyuncs.com"]
}
root@localhost:~# sudo systemctl daemon-reload
root@localhost:~# sudo systemctl restart docker

docker 版本信息

root@localhost:~# docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.6.1-docker)
  scan: Docker Scan (Docker Inc., v0.8.0)

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 19.03.15
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: e25210fe30a0a703442421b0f60afac609f950a3
 runc version: v1.0.1-0-g4144b63
 init version: fec3683
 Security Options:
  apparmor
  seccomp
   Profile: default
 Kernel Version: 5.4.0-86-generic
 Operating System: Ubuntu 20.04.3 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 1
 Total Memory: 1.913GiB
 Name: localhost
 ID: YELC:LIBU:QUVJ:6O46:C4QJ:WUX3:DX3I:TNFU:YELP:XOR5:D7ZV:CFFH
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Registry Mirrors:
  https://0g7sflox.mirror.aliyuncs.com/
 Live Restore Enabled: false

WARNING: No swap limit support

6. Docker 鏡像管理

# 搜索鏡像
root@localhost:~# docker search ubuntu
# 添加版本搜索
root@localhost:~# docker search ubuntu:18.04
  • 下載鏡像

image-20210925171250848

完整地址:服務器地址/項目目錄/鏡像名:版本號

# 查看本地鏡像
root@localhost:~# docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    fb52e22af1b0   3 weeks ago   72.8MB
# 保存鏡像
root@localhost:~# docker save ubuntu -o ubuntu.tar.gz
root@localhost:~# ls
snap  ubuntu.tar.gz
# 刪除鏡像
root@localhost:~# docker image rm ubuntu
Untagged: ubuntu:latest
Untagged: ubuntu@sha256:9d6a8699fb5c9c39cf08a0871bd6219f0400981c570894cd8cbea30d3424a31f
Deleted: sha256:fb52e22af1b01869e23e75089c368a1130fa538946d0411d47f964f8b1076180
Deleted: sha256:4942a1abcbfa1c325b1d7ed93d3cf6020f555be706672308a4a4a6b6d631d2e7
# 導入鏡像
root@localhost:~# docker load < ubuntu.tar.gz
4942a1abcbfa: Loading layer [==================================================>]  75.16MB/75.16MB
Loaded image: ubuntu:latest
# 查看鏡像
root@localhost:~# docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    fb52e22af1b0   3 weeks ago   72.8MB

7. Docker 基礎命令

# 啟動一個容器
root@docker:~# docker run -it ubuntu bash
root@9236732939c5:/#
# 啟動容器不銷毀容器
ctrl + p + q
# 查看正在運行的容器
root@9236732939c5:/# root@docker:~# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED         STATUS         PORTS     NAMES
9236732939c5   ubuntu    "bash"    2 minutes ago   Up 2 minutes             gracious_matsumoto

# 刪除運行中的容器
root@docker:~# docker rm -f 9236732939c5
9236732939c5
root@docker:~# docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

# 隨機端口映射
root@docker:~# docker run -P -d --name nginx001 nginx
4289d3a149543a266fef19f3cc172ceaf1539deed59d9b2bbd1125ed48dce0d3
root@docker:~# netstat -nlpt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      806/systemd-resolve
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      879/sshd: /usr/sbin
tcp6       0      0 :::22                   :::*                    LISTEN      879/sshd: /usr/sbin
tcp6       0      0 :::10003                :::*                    LISTEN      3714/docker-proxy
root@docker:~# lsof -i :10003
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 3714 root    4u  IPv6  54745      0t0  TCP *:10003 (LISTEN)

訪問nginx

image-20210925194209479

查看容器日志

root@docker:~# docker logs nginx001
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2021/09/25 11:39:58 [notice] 1#1: using the "epoll" event method
2021/09/25 11:39:58 [notice] 1#1: nginx/1.21.3
2021/09/25 11:39:58 [notice] 1#1: built by gcc 8.3.0 (Debian 8.3.0-6)
2021/09/25 11:39:58 [notice] 1#1: OS: Linux 5.4.0-86-generic
2021/09/25 11:39:58 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2021/09/25 11:39:58 [notice] 1#1: start worker processes
2021/09/25 11:39:58 [notice] 1#1: start worker process 30
192.168.91.1 - - [25/Sep/2021:11:40:49 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52" "-"
192.168.91.1 - - [25/Sep/2021:11:40:49 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://192.168.91.128:10003/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52" "-"
2021/09/25 11:40:49 [error] 30#30: *2 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 192.168.91.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "192.168.91.128:10003", referrer: "http://192.168.91.128:10003/"

查看容器已經映射的端口

root@docker:~# docker port nginx001
80/tcp -> 0.0.0.0:10003

后台啟動並指定映射端口

root@docker:~# docker run -it -d --name nginx002 -p 8001:80 nginx
d2b11d6a214eab648c93cc397669d06258ac7533075e75ec9ae25fbb1b453c46

創建並進入容器

root@docker:~# docker run -it --name nginx005 -p 8005:80 nginx /bin/bash
root@23e46cdd20e8:/# pwd
/
root@23e46cdd20e8:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

8. Harbor 搭建

harbor安裝需要依賴docker、docker-compose

安裝docker-compose比較簡單

pip3 install docker-compose

修改配置

root@harbor001:/opt/harbor# vim harbor.yml

image-20210925185542881

執行命令安裝


root@harbor001:/opt/harbor# ./install.sh

[Step 0]: checking if docker is installed ...

Note: docker version: 20.10.8

[Step 1]: checking docker-compose is installed ...

Note: docker-compose version: 1.29.2

[Step 2]: loading Harbor images ...
Loaded image: goharbor/redis-photon:v2.3.2
Loaded image: goharbor/nginx-photon:v2.3.2
Loaded image: goharbor/harbor-portal:v2.3.2
Loaded image: goharbor/trivy-adapter-photon:v2.3.2
Loaded image: goharbor/chartmuseum-photon:v2.3.2
Loaded image: goharbor/notary-signer-photon:v2.3.2
Loaded image: goharbor/harbor-core:v2.3.2
Loaded image: goharbor/harbor-log:v2.3.2
Loaded image: goharbor/harbor-registryctl:v2.3.2
Loaded image: goharbor/harbor-exporter:v2.3.2
Loaded image: goharbor/notary-server-photon:v2.3.2
Loaded image: goharbor/prepare:v2.3.2
Loaded image: goharbor/harbor-db:v2.3.2
Loaded image: goharbor/harbor-jobservice:v2.3.2
Loaded image: goharbor/registry-photon:v2.3.2

[Step 3]: preparing environment ...

[Step 4]: preparing harbor configs ...
prepare base dir is set to /opt/harbor
WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to https
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
Generated and saved secret to file: /data/secret/keys/secretkey
Successfully called func: create_root_cert
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir

[Step 5]: starting Harbor ...
Creating network "harbor_harbor" with the default driver
Creating harbor-log ... done
Creating harbor-portal ... done
Creating harbor-db     ... done
Creating registry      ... done
Creating redis         ... done
Creating registryctl   ... done
Creating harbor-core   ... done
Creating nginx             ... done
Creating harbor-jobservice ... done
✔ ----Harbor has been installed and started successfully.----

root@harbor001:/opt/harbor# docker-compose ps
      Name                     Command                       State                     Ports
------------------------------------------------------------------------------------------------------
harbor-core         /harbor/entrypoint.sh            Up (health: starting)
harbor-db           /docker-entrypoint.sh 96 13      Up (health: starting)
harbor-jobservice   /harbor/entrypoint.sh            Up (health: starting)
harbor-log          /bin/sh -c /usr/local/bin/ ...   Up (health: starting)   127.0.0.1:1514->10514/tcp
harbor-portal       nginx -g daemon off;             Up (health: starting)
nginx               nginx -g daemon off;             Up (health: starting)   0.0.0.0:80->8080/tcp
redis               redis-server /etc/redis.conf     Up (health: starting)
registry            /home/harbor/entrypoint.sh       Up (health: starting)
registryctl         /home/harbor/start.sh            Up (health: starting)

docker 登錄harbor鏡像倉庫

image-20210925192228384

http登錄需要修改啟動docker參數

image-20210925192347514

9. Dockerfile 介紹

Dockerfile 是一種可以被認為是通過Docker解釋器編譯的腳本,Dockerfile由一條條的指令組成,每條命令對應Linux一下面的一條命令,Docker 程序將這些 Dockerfile 指令再翻譯成真正的 linux 命令,其有自己的書寫方式和支持的命令,Docker 程序讀取 Dockerfile 並根據指令生成 Docker 鏡像,相比手動制作鏡像,Dockerfile 更能直觀展示鏡像是怎么產生的,有了寫好的 Dockerfile 文件,當后期某個鏡像有額外需求時,只需要改動 Dockerfile 文件,重新構建鏡像即可,避免重復手動制作鏡像。

需要注意每一條指令構建一層,因此每條指令的內容,都是對當前層的描述。

常用指令如下:

FROM 這個鏡像的媽媽是誰?(指定基礎鏡像)
MAINTAINER 告訴別人,誰負責養它?(指定維護者信息,可以沒有)
RUN 你想讓它干啥(在命令前面加上RUN即可)
ADD 給它點創業資金(COPY文件,會自動解壓)
WORKDIR 我是cd,今天剛化了妝(設置當前工作目錄)
VOLUME 給它一個存放行李的地方(設置卷,掛載主機目錄)
EXPOSE 它要打開的門是啥(指定對外的端口)
CMD 奔跑吧,兄弟!(指定容器啟動后的要干的事情)

dockerfile其他指令:

COPY 復制文件
ENV 環境變量
ENTRYPOINT 容器啟動后執行的命令

定制一個nginx的Dockerfile

root@docker:/opt/base# cat Dockerfile
FROM nginx
RUN echo "<h1>Hello, Dockerfile!</h1>" > /usr/share/nginx/html/index.html

root@docker:/opt/base# docker image build -t nginx:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> ad4c705f24d3
Step 2/2 : RUN echo "<h1>Hello, Dockerfile!</h1>" > /usr/share/nginx/html/index.html
 ---> Running in 82d88e82a2c5
Removing intermediate container 82d88e82a2c5
 ---> cc828c032ce9
Successfully built cc828c032ce9
Successfully tagged nginx:v1

root@docker:/opt/base# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
nginx        v1        cc828c032ce9   48 seconds ago   133MB   # 剛才生成的鏡像
centos       latest    5d0da3dc9764   2 weeks ago      231MB
nginx        latest    ad4c705f24d3   3 weeks ago      133MB
ubuntu       latest    fb52e22af1b0   4 weeks ago      72.8MB

RUN 指令執行命令

RUN 指令用來指定命令行命令。其有兩種格式:

  • shell 格式:RUN <命令> , 這種方式就跟執行Linux bash命令行一樣
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec 格式:RUN ["可執行文件", "參數1", "參數2"],這更像是函數調用中的格式。
RUN ["nginx", "-s", "start"]

創建一個redis鏡像

root@docker:/opt/base/redis# cat Dockerfile
FROM debian:stretch

RUN apt-get update
RUN apt-get install -y gcc livc-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

之前說過,Dockerfile 中每一個指令都會建立一層,RUN 也不例外。每一個 RUN 的行為,就和剛才我們手工建立鏡像的過程一樣:新建立一層,在其上執行這些命令,執行結束后,commit 這一層的修改,構成新的鏡像。

而上面的這種寫法,創建了 7 層鏡像。這是完全沒有意義的,而且很多運行時不需要的東西,都被裝進了鏡像里,比如編譯環境、更新的軟件包等等。結果就是產生非常臃腫、非常多層的鏡像,不僅僅增加了構建部署的時間,也很容易出錯。 這是很多初學 Docker 的人常犯的一個錯誤。

Union FS 是有最大層數限制的,比如 AUFS,曾經是最大不得超過 42 層,現在是不得超過 127 層。

上面的 Dockerfile 正確的寫法應該是這樣:

FROM debian:stretch

RUN set -x; buildDeps='gcc libc6-dev make wget' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && mkdir -p /usr/src/redis \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -rf /var/lib/apt/lists/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

首先,之前所有的命令只有一個目的,就是編譯、安裝 redis 可執行文件。因此沒有必要建立很多層,這只是一層的事情。因此,這里沒有使用很多個 RUN 一一對應不同的命令,而是僅僅使用一個 RUN 指令,並使用 && 將各個所需命令串聯起來。將之前的 7 層,簡化為了 1 層。在撰寫 Dockerfile 的時候,要經常提醒自己,這並不是在寫 Shell 腳本,而是在定義每一層該如何構建。

理解構建上下文

在構建docker鏡像時,會看到末尾有一個.. 表示當前目錄,而 Dockerfile 就在當前目錄,因此不少初學者以為這個路徑是在指定 Dockerfile 所在路徑,這么理解其實是不准確的。如果對應上面的命令格式,你可能會發現,這是在指定 上下文路徑。那么什么是上下文呢?

當我們進行鏡像構建的時候,並非所有定制都會通過 RUN 指令完成,經常會需要將一些本地文件復制進鏡像,比如通過 COPY 指令、ADD 指令等。而 docker build 命令構建鏡像,其實並非在本地構建,而是在服務端,也就是 Docker 引擎中構建的。那么在這種客戶端/服務端的架構中,如何才能讓服務端獲得本地文件呢?

這就引入了上下文的概念。當構建的時候,用戶會指定構建鏡像上下文的路徑,docker build 命令得知這個路徑后,會將路徑下的所有內容打包,然后上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包后,展開就會獲得構建鏡像所需的一切文件。

如果在 Dockerfile 中這么寫:

COPY ./package.json /app/

這並不是要復制執行 docker build 命令所在的目錄下的 package.json,也不是復制 Dockerfile 所在目錄下的 package.json,而是復制 上下文(context) 目錄下的 package.json

因此,COPY 這類指令中的源文件的路徑都是相對路徑。這也是初學者經常會問的為什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app 無法工作的原因,因為這些路徑已經超出了上下文的范圍,Docker 引擎無法獲得這些位置的文件。如果真的需要那些文件,應該將它們復制到上下文目錄中去。

現在就可以理解剛才的命令 docker build -t nginx:v3 . 中的這個 .,實際上是在指定上下文的目錄,docker build 命令會將該目錄下的內容打包交給 Docker 引擎以幫助構建鏡像。

如果觀察 docker build 輸出,我們其實已經看到了這個發送上下文的過程:

$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...

一般來說,應該會將 Dockerfile 置於一個空目錄下,或者項目根目錄下。如果該目錄下沒有所需文件,那么應該把所需文件復制一份過來。如果目錄下有些東西確實不希望構建時傳給 Docker 引擎,那么可以用 .gitignore 一樣的語法寫一個 .dockerignore,該文件是用於剔除不需要作為上下文傳遞給 Docker 引擎的。

在默認情況下,如果不額外指定 Dockerfile 的話,會將上下文目錄下的名為 Dockerfile 的文件作為 Dockerfile。可以在編譯時,使用 -f 參數指定 Dockerfile。

一般大家習慣性的會使用默認的文件名 Dockerfile,以及會將其置於鏡像構建上下文目錄中。

Docker build 其他構建方式

1、直接使用github地址構建

# $env:DOCKER_BUILDKIT=0
# export DOCKER_BUILDKIT=0

$ docker build -t hello-world https://github.com/docker-library/hello-world.git # master:amd64/hello-world

Step 1/3 : FROM scratch
 --->
Step 2/3 : COPY hello /
 ---> ac779757d46e
Step 3/3 : CMD ["/hello"]
 ---> Running in d2a513a760ed
Removing intermediate container d2a513a760ed
 ---> 038ad4142d2b
Successfully built 038ad4142d2b

2、用給定的 tar 壓縮包構建

$ docker build http://server/context.tar.gz

3、從標准輸入中讀取 Dockerfile 進行構建

$ docker build - < Dockerfile
或者
$ cat Dockerfile | docker build -

如果發現標准輸入的文件格式是 gzipbzip2 以及 xz 的話,將會使其為上下文壓縮包,直接將其展開,將里面視為上下文,並開始構建。

10. Dockerfile 指令詳解

COPY 復制文件

格式:

COPY [--chown=<user>:<group>] <源路徑>... <目標路徑>
COPY [--chown=<user>:<group>] ["<源路徑1>",... "<目標路徑>"]

和RUN指令一樣,有兩種格式,一種類似命令行,一種類似於函數調用。

COPY 指令將從構建的上下文目錄中文件或目錄拷貝到新一層的鏡像內指定的位置。比如:

COPY nginx.tar.gz /opt/
# 可以指定多個文件進行拷貝,甚至可以是通配符,只要滿足Go 的 filepath.Match
COPY *.conf /etc/nginx/conf.d/

注意:目標目錄不需要事先創建,目標目錄不存在會在復制文件前先進行創建缺失的目錄。

而且,源文件的各種文件權限都會保留,比如讀寫執行權限,文件的變更時間等,特別是構建相關文件都是在使用git進行管理的時候,這個特性對於定制鏡像很有用。在使用該指令的時候,還可以加上 --chown=<user>:<group>選項來改變文件的所屬用戶以及所屬組。

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

如果源文件為文件夾,復制時不是復制該文件夾,而是將文件夾的內容復制到目標路徑。

ADD 更加高級的復制指令

ADD 指令和 COPY 指令的格式和性質基本一樣,但是ADD 多了一些功能。

比如源文件可以是一個URL,這時Docker引擎會試圖拉去並下載這個鏈接文件,並放到目標目錄,下載后的權限自動設置為600,如果這並不是想要的權限,那么還需要增加額外的一層 RUN 指令進行權限調整,如果下載的是個壓縮包,會自動解壓。在 Docker 官方的 Dockerfile 最佳實踐文檔 中要求,盡可能的使用 COPY,因為 COPY 的語義很明確,就是復制文件而已,而 ADD 則包含了更復雜的功能,其行為也不一定很清晰。最適合使用 ADD 的場合,就是所提及的需要自動解壓縮的場合。

CMD 容器啟動命令

CMD 指令格式:

# 1、shell格式:CMD <命令>
CMD 

exec

參考:
https://time.geekbang.org/column/article/14642

使用 Dockerfile 定制鏡像 - Docker —— 從入門到實踐 (gitbook.io)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM