Docker Notes
31 May 2020
Docker 簡介
簡介
重點Reference
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掛載
常用指令
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時,做了以下事情
- 檢查本地端有沒有該image,沒有的話就去registry下載
- 使用image建立container
- 分配到一個檔案系統,並在read layer加上一層read-write layer
- 從原主機設定bridge連接container跟主機(host)
- 執行使用者指定的應用程式,執行完時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
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
--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
link
可以讓兩個容器互聯
$ 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)
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時要注意的
- 不要安裝不必要的package,不然image會變很大,讓image建造時間變久、佔據空間更大
- 一個container只做一件事,所以像是web app,就不應該把db跟web建在同個image內。當然有些像是apache他本身會開很多的process,讓container行為複雜。如果是有需要很多container之間的聯繫的狀況,可以參考Docker Container network
- RUN,COPY,CMD會再image增加read layer,所以應該要minimize這些指令的使用,減少image的read layer
- 為了達到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
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裡的資料也就不見。