Docker Notes

31 May 2020

Docker 簡介

簡介

重點Reference

Docker run documentation

Dockerfile 相關參數

Best Practice for Dockerfile

Docker engine

達到OS level的虛擬化,

Docker engine是負則虛擬化、containerize應用程式的程式。是以client-server mode運行。
他會跑一個dockerd的daemon process,可以透過API或者cli(用docker指令)的方式跟dockerd做互動

VM & Container

VM: 打包整個作業系統,是implement在hardware層級的虛擬化,由hypervisor來模擬出一個類似hardware的環境
Container: 打包整個應用程式,implement在OS層及的虛擬化

因此vm虛擬化一個新的OS後,還會下載很多跟應用程式無關的東西(很多沒必要的bin,lib)
而Container只會載應用程式需要的lib,bin(在Dockerfile裡面specify),所以可以達到很輕量級的虛擬化,他是一個跑在OS userspace上的isolated的process

因為Container相比減少了很多在執行應用程式上不需要的資源,所以在執行、開啟速度上,跟實際performance都是比VM好非常多。而且更是輕量級的虛擬化,不用載過多多餘的library或binary file。

簡而言之,VM打包作業系統,Container打包應用程式。

Docker registry

docker版的github,大家把自己的image push上去,要的人再去下載相關的image。 有public registry跟private registry,其中最有名的public registry即Dockerhub

Data volume

  • volume可以在容器之間共享和重用,因此如果有使用volume的話
  • 對volume的修改會馬上生效
  • 對volume的更新,不會影響image
  • volume會一直存在,直到沒有容器使用

因此使用volume有以下好處

  • Container掛掉或刪除時,Data並不會消失,因為volume mount在本地端,資料還在
  • Data可以輕鬆使用Container裡的tool,舉例而言,自己沒有gcc,你可以再Container裡裝gcc,然後c file mount到container裡,這樣你就可以很輕鬆的用Docker裡的gcc來編譯來取的binary file

把host的資料夾對應到container裡,但dockerfile裡並沒有相關的語法,只能用copy的方式

Host volume

不用另外創一個volume,直接在docker run時specify volume位置

$ docker run -d -v <host dirpath>:<container idrpath>:<file-opt> --name <app_name> python crawl.py
<file-opt>: ro: read-only, wo: write-only

volume create(法2)

先創一個volume,在docker run時等於把上面指令的host dirpath改成volume的名稱

$ docker volume create --name <volume-name>
$ docker volume ls

Reference

Data Volume container

用於有些更新的資料要在容器間共享,就可以用Data Volume container。 他其實就是一個Container,可以讓其他container掛載

Reference

常用指令

download

$ docker pull <image:tag>

建立image

$ docker build -t <Newimagename> .
or
$ docker build -t <Newimagename> -f <PathOfDockerFile> # 不一定名稱要是Dockerfile,只要有specify哪個檔案即可

Log Inspection

$ docker image ls: lists all images
$ docker ps -a : 看目前host有幾個container(有-a的話,活著、掛掉的都會列出來,沒有的話只會列出活著的)
$ docker logs <container id> : 看container的log,也可以看到container裡面的service跑在哪個port上
$ docker inspect <container id> : 看container設定
$ docker port : 看port使用情況

Remove & Delete

$ docker stop <container id> : 停止container
$ docker rm <container id> : 刪除container
$ docker rmi <image>

Export & import

$ docker export <container id> > output.tar : 把container包成一個檔案出來,之後要用舊import即可
$ docker import output.tar -<name:TAG> : 把之前包的container檔變回container

跑起container - docker run

用docker run跑起container時,做了以下事情

  1. 檢查本地端有沒有該image,沒有的話就去registry下載
  2. 使用image建立container
  3. 分配到一個檔案系統,並在read layer加上一層read-write layer
  4. 從原主機設定bridge連接container跟主機(host)
  5. 執行使用者指定的應用程式,執行完時container掛掉
$ docker run -i -t --rm ubuntu:18.04 /bin/bash  # 測試指令
$ docker run -t -i -v <HostDIR>:<containerDIR> --name <containerName> -p <hostport:containerport> /bin/bash

$ docker run -t -i -v <HostDIR>:<containerDIR> --name <containerName> -p <hostport:containerport>  -d <imagename>

-t: 開一個虛擬terminal使用
-i: interactive,保持stdin是開的,能夠讓我們輸入的指令傳給container
-v: 對應host的資料夾,把資料夾share到container內
-p <hostport>:<containerport> 可以指定對應的port
-d : detach mode: 就是跑起container,然後terminal還給妳,可以想成背景執行的樣子。當root執行的process結束時,container就掛掉
--name : 給container新名字
--rm : 當container掛掉時,把container刪掉,不然他還是會存在電腦裡(docker ps -a)
/bin/bash: container執行的程式,在這個指令下,退出container後,container就掛了,因為等於送signal exit給container。
memory: 可以限制container使用的memory量的上限
CPU: 限制CPU的佔據量
--privileged: 給與root更高權限,能夠修改硬體設定,如tcp offload

Reference

PIDNamespace

docker container對於pid(process id)也是isolate的,所以在container裡面跑的process他的id是獨立的,不會佔用host的pid(像網路NAT的概念)。但如果想要自己container裡的pid實際上加入例如host或其他container的namespace,就可以做以下specify。 融入其他process namespace(例如host)的好處是,該process就可以看到host裡其他的process,因為在同個process namespace,這時如果在docker container裡跑gdb,那就可以attach host上的process了。

$ docker run --pid=<host|container_id>
host: 和host(主機共用同個process id namespace)
container_id: 和那個container共用一個process id namespace

Reference

Network

對於docker run port的設定,除了上述提到的hostport:containerport外還有以下的進階用法

1. 指定container port對應的host ip & port
$ docker run  -p <hostip:hostport>:<containerport>

2. 或者ip上每個port都對應到container的container port
$ docker run -p <hostip>::<ContainerPort>

3. 設定對應到的container的port是udp,tcp
$ docker run -p <hostport>:<ContainerPort>/udp

Reference

--dns=[]           : Set custom dns servers for the container
--network="bridge" : Connect a container to a network
                      'bridge': create a network stack on the default Docker bridge
                      'none': no networking
                      'container:<name|id>': reuse another container's network stack
                      'host': use the Docker host network stack
                      '<network-name>|<network-id>': connect to a user-defined network
--network-alias=[] : Add network-scoped alias for the container
--add-host=""      : Add a line to /etc/hosts (host:IP)
--mac-address=""   : Sets the container's Ethernet device's MAC address
--ip=""            : Sets the container's Ethernet device's IPv4 address
--ip6=""           : Sets the container's Ethernet device's IPv6 address
--link-local-ip=[] : Sets one or more container's Ethernet device's link local IPv4/IPv6 addresses

可以讓兩個容器互聯

$ docker run -d -P --name web --link db:db training/webapp python app.py

跑起一個webapp和DB互聯

--link <container name>:<alias>

這種互聯方式不用specify port,因為port的參數是container和host之間,所以link能達到兩個container自己互聯而不用另外跟host有連結,不用透過host連到另一個container。

可以用cat /etc/hosts 或者docker run時指令打env來看兩個容器的連接狀況
其他container的name/ip會存在/etc/hosts裡面,相當於local端的dns的概念。

Bridge

Docker各個container和host連接,示意圖如下。

  • docker啟動時會跑一個docker0的NIC,擁有private ip,如172.17.42.1/16, 255.255.0.0
  • 當啟動新的container時,Docker0會和新的container建立一個veth pair介面,而container端的連接的NIC名稱會從veth變成eth0(但只是改名,實際上還是連到docker0上的veth pair)

Reference

Docker Network setting

Docker documentation

Docker bridge network

Attach to container(docker exec)

$ docker exec -it <container_name> bash|sh

這樣能夠進入container的console,看container狀況

Dockerfile

透過Dockerfile,我們可以建造屬於自己的image,在現有的image下加一些自己想要的東西。
透過dockerfile建造image時,會建造多層的read layer。 當在跑Container時,會在現有的image上再加一層write layer(Container layer)。所有在Container裡的寫入都是寫到這層layer。所以依刪掉東西就都沒有

以下有幾點是在寫Dockerfile時要注意的

  1. 不要安裝不必要的package,不然image會變很大,讓image建造時間變久、佔據空間更大
  2. 一個container只做一件事,所以像是web app,就不應該把db跟web建在同個image內。當然有些像是apache他本身會開很多的process,讓container行為複雜。如果是有需要很多container之間的聯繫的狀況,可以參考Docker Container network
  3. RUN,COPY,CMD會再image增加read layer,所以應該要minimize這些指令的使用,減少image的read layer
  4. 為了達到cache busting),所以最好把apt-update和apt install寫在同行,即 apt-get update & apt-get install…
  • 開頭可以用#註解
  • 分成4部分
    • image information
    • maintainer info
    • image operation
    • command for container

EX:

# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# Image
FROM ubuntu

# 維護者: docker_user <docker_user at email.com> (@docker_user)
MAINTAINER docker_user docker_user@email.com

# image operation
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf

ENTRYPOINT ["s3cmd"] # 基本上作用跟cmd類似,但是這是主要command,要用的話可以用ENTRYPOINT表示要執行的指令,CMD表示parameter,對於很穩定、default command可以用ENTRY POINT,額外的設定、param可以用CMD

# command when executing a container
CMD /usr/sbin/nginx

Dockerfile 相關參數

Best Practice for Dockerfile

RUN CMD ENTRYPOINT

這三種指令都有兩種撰寫跟執行的模式, Shell和Exec格式

Shell

基本上就是直接打指令(如下), docker在解讀時會自動append /bin/sh -c在那行指令上面

RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
Exec

格式就是會用中括號把指令括起來, 每個空格會把指令split成一個元素, 所以寫法如下, 這種寫法docker在解讀時就不會把/bin/sh這段prefix加在指令前, 所以等於會直接執行那個執行檔而不是啟用shell process.

RUN ["apt-get", "install", "python3"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]
ENV name John Dow
ENTRYPOINT ["/bin/echo", "Hello, $name"]
使用Exec格式 因為是直接執行binary, 所以shell定義的variable不會替換
正確寫法為
ENTRYPOINT ["/bin/bash", "-c", "echo Hello, $name"]
RUN

基本上就是跑那個指令, 然後她會在既有的layer上多一層Read-only layer

CMD

跑起Container時的default command, 如果跑起container沒specify要跑的指令時, 就會跑這個

ENTRYPOINT

跟CMD大同小異, 差別是如果docker container跑起來時有specify指令跟arument, ENTRYPOINT裡面的argument並不會被忽略

Reference goinbigdata.com

dockerignore

跟.gitignore相同概念,就是specify在裡面的資料夾、檔案在docker build時不會加入Image裡。docker push也是依樣,不會push到遠端的registry。

Storage drivers

這部分介紹Docker image是怎麼build,container的data storage。
從上面Dockerfile撰寫建議中,盡量少用ADD,RUN,COPY指令也會在這裡得到說明。

Docker image是層層layer建造的,每層都是read-only layer(image layer),只有最後一個layer是write layer(container layer)。
因此container跟image layer的一個差別就在於有沒有那層write layer。一個image可以build多個container,各個container有自己的write layer所以不同container不會互相影響,但是一但container被delete,那層write layer裡的資料也就不見。

Reference