天天看點

docker 實踐手冊準備環境操作指令鏡像建構docker-compose參考

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配置