toc
準備環境
安裝運作
開始前需要了解 docker的一些 基本概念
- 鏡像(
)Image
- 容器(
)Container
- 倉庫(
)Repository
以 ubuntu 安裝 docker 為例:
$ sudo apt install docker.io
$ sudo groupadd docker
$ sudo usermod -aG docker $USER # 否則隻能以 root 操作 docker
## 重新開機終端
$ docker run hello-world
複制
配置環境
$ vim /etc/docker/daemon.json
$ sudo systemctl daemon-reload # 重載配置
$ sudo systemctl restart docker # 重新開機docker server
複制
使用鏡像加速器
國内從docker hub拉取鏡像困難時,内網其他鏡像資源等
{
"registry-mirrors": [
"https://registry.docker-cn.com" # dockerhub 國内
]
}
複制
修改 docker 目錄
{
"data-root": "/data/docker",
}
複制
sudo rsync -avz /var/lib/docker /data/docker ## 遷移目錄
複制
限制容器 log 大小
避免運作容器log 無限增長
"log-driver": "json-file",
"log-opts": {
"max-size": "10m", # 10M
"max-file": "10" # 10個
}
複制
操作指令
基本指令
## 拉取鏡像,私有鏡像需要先登入
$ docker pull [Docker Registry 位址[:端口号]/]倉庫名[:标簽]
## 運作鏡像, -it 互動運作/ -d 背景運作, --rm 容器結束後銷毀
$ docker run -it --rm ubuntu:18.04 /bin/bash
## 列出本地鏡像
$ docker image ls
## 列出所有容器
$ docker ps -a
$ docker start/stop 容器id/名
## 檢視log
$ docker logs -f 容器id # 從啟動開始,
# --tail 10 顯示曆史10,而不是所有.. 詳細 help
## 進入背景執行的容器, -i 互動模式, -t 配置設定終端
$ docker exec -it 容器id /bin/bash
## 導出導入鏡像,鏡像id
$ docker save 7691a814370e > ubuntu.tar
$ docker load -i ubuntu.tar #導入鏡像, 名和tag 同導出
## 導出導入容器,容器id
$ docker export 7691a814370e > ubuntu.tar
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0 #導入為鏡像
## 删除容器,鏡像
$ docker rm 容器id
$ docker container prune ## 清理所有停止容器
$ docker rmi 鏡像id [-f]
$ docker system prune ### 清理所有無用容器、緩存
複制
容器網絡
網絡指令參考
網絡模式
- bridge: 預設模式,獨立network namespace,通過 docker0 虛拟網橋,主機與容器通信,
- host: 容器與主機共用 network namespace
- Containner: 新建立容器和另外一個容器共享同一個 network namespace,兩個容器可以通過 lo 直接通信
- NONE: 容器有自己的network namespace,但是沒有配置網卡,ip路由資訊,需自己手動配置
網絡模式
容器連接配接外部
容器通過 net 可以直接通路外部網絡,主機配置:
$sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
複制
外部連接配接容器
外部連接配接容器,需要容器通過 -p(小寫指定端口)/-P(大寫随機配置設定端口) 參數指定對外暴露端口,映射到主機上,
# docker run -d -p [host]:port:c_port/udp xxxx
$ docker run -d \
-p 5000:5000 \
-p 3000:80 \
training/webapp \
python app.py
複制
容器互聯
容器連接配接互聯,推薦使用者自定義網絡,不要使用
--links
# 建立自定義網絡
$ docker network create -d bridge my-net
$ docker run --name cc1 --network my-net ubuntu sh
$ docker run --name cc2 --network my-net ubuntu sh
## 進入 cc1 中,直接執行 ping cc2, 可以ping 通了
## 通過網絡,對應容器名在其他容器中會解析為配置設定的 ip
## 多個容器互聯,使用docker-compose,自動配置設定網絡,友善
複制
資料管理
容器與主機外部進行資料互動方式
資料卷
## 建立資料卷
$ docker volume create my-vol
$ docker volume ls
## 檢視資料卷資訊
$ docker volume inspect my-vol
## 啟動容器挂載資料卷
$ docker run -d -P \
--name web \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
$ $ docker inspect web # --> "Mounts"下
## 删除資料卷
$ docker volume rm my-vol
$ docker volume prune ## 無主資料卷清理
複制
資料卷
是被設計用來持久化資料的,它的生命周期獨立于容器,Docker 不會在容器被删除後自動删除
資料卷
,并且也不存在垃圾回收這樣的機制來處理沒有任何容器引用的
資料卷
。如果需要在删除容器的同時移除資料卷,可以在删除容器的時候使用
docker rm -v
這個指令。
資料卷進階
資料卷容器
容器通過
--volumes-from
挂載到某個容器A創已經建資料卷上,容器A 為資料卷容器。
容器A 不需要處于運作狀态,
挂載本機目錄
## 挂載本機目錄(絕對路徑,預設讀寫權限
$ docker run -d -P \
--name web \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py
## 設定權限
$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py
## 直接挂載一個檔案
$ docker run --rm -it \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:18.04 \
bash
複制
鏡像建構
使用 dockerfile
通過 commit 可以建構 image,但是每次 commit 都是疊加了一個層,而且使用 commit 建構 image,後期不确定 image 中包含了什麼操作。
使用 dockerfile 描述建構的 image,每一個 RUN 實際也會對應疊加一層,是以建構時,把多個指令放在同一個 RUN, 減少無意義中間層(image 包含層數是有限制的),還要注意建構指令結尾記得清理無用的檔案,避免構造的 image 臃腫。
鏡像建構上下文
建構鏡像時使用如下指令,
$ docker build -t xx/xx .
docker build 中這個 . 是指定建構鏡像的上下文路徑(不要了解為目前路徑),由于docker運作時是使用 c/s 模式,當在指令行執行 docker build,實際是執行遠端調用,通知 docker 引擎完成實際任務,請求時會把上下文路徑下的檔案打包發給服務端(docker引擎)。
)
比如建構鏡像中時常有 ADD, COPY, 這些指令将指定檔案拷貝到鏡像中,并不是拷貝執行 docker build 目前目錄下的檔案,而是從打包過去的檔案尋找。
是以,如果這樣寫
ADD ../file.xx /root/
是無法工作的,因為已經超出了上下文,請求是并沒有打包給引擎,自然無法找到。
基于上下文這個概念,建構鏡像時,應該保持指定路徑下隻包含需要的檔案,避免打包無關檔案(或添加 .dockerignore 檔案),這也是通常建立個目錄的原因
至于指定 dockerfile,使用參數 -f
$ docker build -t nginx:v3 .
複制
docker build 可以直接指定 git rep 建構、tar包建構,等;
一般來說,使用 Dockerfile 建構鏡像時最好是将 Dockerfile 放置在一個建立的空目錄下。然後将建構鏡像所需要的檔案添加到該目錄中。為了提高建構鏡像的效率,可以在目錄下建立一個 .dockerignore 檔案來指定要忽略的檔案和目錄。.dockerignore 檔案的排除模式文法和 Git 的 .gitignore 檔案相似。
建構腳本的指令
dockerfile 每執行一條指令就會建立一層,是以将多個指令合并,減少層數過多,
From 指定基礎鏡像
設定工作路徑
workdir xxx
設定目前工作路徑(以後各層也一樣),目錄不存在會自動建立
dockerfile 不同于shell,前後兩行是不同執行環境,是以之後無法在 app 下找到 install.sh
RUN cd /app
RUN copy install.sh .
複制
Run 運作指令
- shell 格式: RUN echo "xx" > xx.md
-
exec 格式:RUN "echo", "xx",">","xx.md"
shell 格式實際執行會包裝為 "sh","-C", "xx",環境變量什麼能被shell解析
RUN ["echo", "$HOME"] # 沒有shell解析,列印 "$HOME"
RUN echo $HOME # shell解析,列印出路徑
複制
exec 要使用""括起來,因為會被解析為json
合并多條指令,避免鏡像建立太多層,
FROM debian:stretch
RUN 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 ## 清理安裝内容
複制
copy 和 add 的差别
copy 将上下文目錄中的檔案、目錄複制到新一層鏡像内,
COPY package.json /usr/src/app/
COPY hom* /mydir/
COPY hom?.txt /mydir/
複制
<目标路徑> 可以是容器内的絕對路徑,也可以是相對于 WORKDIR 指令設定的工作路徑,不需要事先建立,如果目錄不存在會在複制檔案前先行建立缺失目錄。使用 COPY 指令,源檔案的各種中繼資料都會保留。比如讀、寫、執行權限、檔案變更時間等。
在使用該指令的時候還可以加上 --chown=<user>:<group> 選項來改變檔案的所屬使用者及所屬組。
COPY --chown=66:mygroup files* /mydir/
add 和 copy 一樣,但是在其基礎加了其他功能:
- add 的原路徑可以是 URL,建構時會自動拉取,設定權限為 600,如果是壓縮不會自動解壓
- add 如果是個壓縮包,會自動解壓
另外,add 可能導緻建構緩存失效,是以:
大部分情況使用 copy,語義明确,需要解壓縮再使用add 就好;
entrypoint 和 cmd 差别
entrypoint 和cmd 都和run一樣,支援 shell 和exec格式,
docker 不是虛拟機,容器中的應用應該以前台執行(容器中沒有背景運作的服務),啟動時需要給出運作的bin和參數,通過 entrypoint 和 cmd 指令來實作,一般推薦用exec格式,shell 格式容易混淆前背景執行。
例子:
cmd ["echo","echo_cmd"]
entrypoint ["echo", "echo_entry"]
複制
# 以上cmd和entrypoint都設定,運作時不帶參數,實際運作指令:
echo echo_entry echo echo_cmd ## $entrypoint $cmd
# 以上cmd和entrypoint都設定,運作時帶參數 hello,實際運作指令:
echo echo_entry hello ## $entrypoint hello, cmd 被覆寫
# entrypoint設定,運作時不帶參數,實際運作指令:
echo echo_entry ## $entrypoint
# entrypoint設定,運作時帶參數 hello,實際運作指令:
echo echo_entry hello ## $entrypoint hello
# cmd設定,運作時不帶參數,實際運作指令:
echo echo_cmd ## $cmd
# cmd 設定,運作時帶參數 hello,實際運作指令:
hello (報錯,除非hello是可執行的)
# cmd 設定,運作時帶參數 echo xxx,實際運作指令:
echo xxx
複制
- 在運作鏡像時,如果跟着其他參數,cmd就會被覆寫,而如果想覆寫 entrypoint 需要指定
--entrypoint
- 如果有 entrypoint,cmd 會作為預設參數傳遞給 entrypoint 作為執行參數;運作時傳入參數,cmd 就會被覆寫,入口依然是entrypoint
- 如果沒有 entrypoint,cmd 直接作為預設執行入口+參數;運作時執行入口+參數可以被傳入替換
用 entrypoint 指定入口,用 cmd 指定預設參數,使鏡像可以想工具一樣使用,以及確定鏡像啟動一定做好準備工作(比如設定entryppoint 固定為初始化腳本,根據cmd傳入去指定之後的事)
參考:
https://zhuanlan.zhihu.com/p/30555962
https://yeasy.gitbooks.io/docker_practice/image/dockerfile/entrypoint.html
設定環境變量
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
複制
傳遞參數
ARG MY_ENV="default/xxx" ## dockerfile 聲明參數
ENV $MY_ENV ## 引用參數
複制
建構時傳入:
--build-arg MY_ENV="XXX"
健康檢查
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
複制
忽略錯誤
RUN make; exit 0
複制
onbuild 指令
指定目前鏡像不運作,在目前鏡像作為基礎鏡像建構其他鏡像才運作的指令;
https://yeasy.gitbooks.io/docker_practice/image/dockerfile/onbuild.html
建構緩存問題
Docker建構是分層的,一條指令一層,在docker build 沒有帶
--no-cache=true
指令的情況下如果某一層沒有改動,Docker就不會重新建構這一層而是會使用緩存。
通過适當的拆分指令,達到分層利用緩存,提高建構速度。
copy go.mod .
RUN go mod download # 先拷貝go.mod下載下傳,
# 後面如果依賴不變,則不需要重複download
copy . .
RUN make
複制
但是有些時候也要避免cache帶來問題,如不要把 update 和 install 拆分,不然後面新增應用,但是update隻會第一次執行。
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*
複制
分階段建構
在同一個鏡像中完成應用建構和執行,可能導緻鏡像臃腫,代碼洩露等問題,是以需要多階段建構;
建構階段,建構鏡像中完成應用建構;之後将建構産物拷貝到運作鏡像(運作鏡像隻包含運作需要的依賴,小巧)
FROM golang:1.9-alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/helloworld/app .
CMD ["./app"]
複制
遠端倉庫
$ sudo docker login --username=xx url
$ docker pull hub/image:xxx
$ docker push hub/image:xxx
複制
docker-compose
version: "3"
services:
loraserver:
image: ccr.ccs.tencentyun.com/lora/networkserver:${run_ver}
volumes:
- ${work_path}/configuration/loraserver:/etc/loraserver
- /etc/localtime:/etc/localtime:ro
environment:
- JOIN_SERVER.DEFAULT.SERVER=http://appserver:8003
appserver:
image: ccr.ccs.tencentyun.com/lora/appserver:${run_ver}
logging:
driver: "json-file"
options:
max-size: "200m"
max-file: "10"
ports:
- ${as_api_port}:8080
volumes:
- asdata:/lora-app-server
- /etc/localtime:/etc/localtime:ro
depends_on:
- loraserver
volumes:
asdata:
複制
參考
指令
參考
資料
資料卷 或者 直接挂載本地目錄
網絡
參考
參考
- docker 如何友善開發測試
- docker docs
- 官方鏡像彙總庫
- 入門實踐
- dockerfile 官方參考
- dockerfile 最佳實踐
- logP配置