docker 使用實踐

準備環境

安裝運行

開始前需要了解 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  ## 遷移目錄

翻牆設置代理

網路問題,設置docker pull代理
其他參考 https://blog.csdn.net/styshoo/article/details/55657714

限制容器 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=: 選項來改變文件的所屬用戶及所屬組。
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:

參考

命令

參考

數據

數據卷 或者 直接掛載本地目錄

網絡

參考

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章