Leon's Blogging

Coding blogging for hackers.

Docker - Dockerfile

| Comments

docker 讓應用程式佈署在軟體容器下的工作可以自動化進行,藉此在Linux作業系統上,提供一個額外的軟體抽象層,以及作業系統層虛擬化的自動管理機制。

Docker 的優點

  • 秒級實作
  • 對系統資源的使用率很高,一台主機可以同時執行數千個 Docker 容器
  • 更快速的交付和部署
    • 使用一個標準的映像檔來建立一套開發容器,開發完成之後,維運人員可以直接使用這個容器來部署程式碼。
  • 更有效率的虛擬化
    • 不需額外的虛擬化支援,它是核心層級的虛擬化
  • 更輕鬆的遷移和擴展
    • Docker 容器幾乎可以在任意的平台上執行
  • 更簡單的管理

Docker vs VM

  • Docker 僅載入所需要的函式庫與執行檔,而不像VM需要安裝大容量的作業系統
  • VM是用硬體端的hypervisor技術來同時執行多個虛擬主機,一般可預見需要大量的運行資源。Docker則是用軟體端的作業系統來實現虛擬分割的技術,由於僅載入核心的函式庫

Basic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 查看版本
docker --version
docker-compose --version
docker-machine --version

# 從 hub 取得所需要的 images :後面為版本號,若沒加則會拉最新的
docker pull ubuntu:12.04

# 搜尋 Image
docker search
docker search ubuntu -f is-official=true # is-official=true 官方的意思

# container (有些 container 可改 ps)
docker container ls                              # List all running containers
docker container ls -a                           # List all vcontainers, even those not running
docker container stop <containerID>              # Gracefully stop the specified container
docker container kill <containerID>              # Force shutdown of the specified container
docker container rm <containerID>                # Remove specified container from this machine
docker container rm $(docker container ls -a -q) # Remove all containers; -q show all ID
docker start <containerID>
docker stop <containerID>

# images
docker image ls -a                       # List all images on this machine
docker image rm <image id>               # Remove specified image from this machine
docker image rm $(docker image ls -a -q) # Remove all images from this machine
docker rmi <imageID>|<imageName>
docker rmi nginx
docker rmi nginx -f

# 顯示 container 的資訊
docker inspect <containerID>

# 跳回 terminal 而不關閉 container
(ctrl + p) + (ctrl + q)

# 回去 container
docker attach <containerID>

# 查看 docker container 的 IP
進去 container 執行 cat /etc/hosts

# 複製檔案到外面
docker cp <containerId>:<containerPath> <localPath>

# 複製檔案到 container 裡面
docker cp  <localPath> <containerId>:<containerPath>

Run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 執行 docker ,若本地沒有 image,就會自動去 Docker Hub pull 下來,如果沒有指定 TAG,預設使用 latest
docker run -t -i ubuntu:12.04 bash
# -t 在容器中分配一個虛擬終端(pseudo-tty)並綁定到容器的標準輸入上
# -i 建立與容器標準輸入(STDIN)的互動連結,讓容器的標準輸入保持打開
# 執行後啟動 bash

docker run -d -p 80:80 --name webserver nginx
# -p 80:80 (主機 port, container port)
# --name webserver
# -d daemon 模式,放到背景去跑

docker run -it -v ~/Downloads: ubuntu:12.04 bash
# 建立容器並啟動,且掛載本地目錄(local 在前,container 在後)

# 加上 -rm 可以在 container 結束後自動刪除
docker run -it --rm ubuntu:latest bash

# 如果 container 停下來會自動 restart
--restart=always

# net 設定網路範圍
docker run --net=bridge -it ubuntu
  • none: 在執行 container 時,網路功能是關閉的,所以無法與此 container 連線 container: 使用相同的 Network Namespace,所以 container1 的 IP 是 172.17.0.2 那 container2 的 IP 也會是 172.17.0.2
  • host: container 的網路設定和實體主機使用相同的網路設定,所以 container 裡面也就可以修改實體機器的網路設定,因此使用此模式需要考慮網路安全性上的問題
  • bridge: Docker 預設就是使用此網路模式,這種網路模式就像是 NAT 的網路模式,例如實體主機的 IP 是 192.168.1.10 它會對應到 Container 裡面的 172.17.0.2,在啟動 Docker 的 service 時會有一個 docker0 的網路卡就是在做此網路的橋接。
  • overlay: container 之間可以在不同的實體機器上做連線,例如 Host1 有一個 container1,然後 Host2 有一個 container2,container1 就可以使用 overlay 的網路模式和 container2 做網路的連線。

Attach

1
2
3
# 連到 container 最後執行
# 當多個窗口同時 attach 到同一個容器的時候,所有窗口都會同步顯示。當某個窗口因命令阻塞時,其他窗口也無法執行操作了
docker attach <imageID>|<imageName>

Exec

1
2
# 進去 container 執行 bash
docker exec -it <imageID>|<imageName> bash

Volume

1
2
# Volume mapping to map the local directory /opt/datadir to /var/lib/mysql
docker run -v /opt/datadir:/var/lib/mysql mysql

log

1
2
# 看 container log,-f=follow, -t=time
docker logs -tf <imageID>|<imageName>

export

1
2
3
docker export nginx > nginx.tar
cat nginx.tar | docker import - nginxbak
# nginxbak 是 Import 進 Docker 取得 Image Name

stats

查看 Docker Container 的資源使用狀態

1
docker stats <container name> | <container id>

建立映像檔

1.修改已有映像檔

進去現有的 container,加入新的套件,再用此 container build 新的 images

1
2
3
4
5
6
7
# commit 建立新的 images
# docker commit -m '<message>' -a '<author>' <containerID> <tag>
docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2
# -m 指定提交的說明信息,跟我們使用的版本控制工具一樣;
# -a 可以指定更新的使用者信息
# 之後是用來建立映像檔的容器的 CONTAINER ID
# 最後指定新映像檔的名稱和 tag 。建立成功後會印出新映像檔的 ID。

2.利用 Dockerfile 建立映像檔

Tag and Publish the image

要 push 的話,必須將 image tag 成 username/imagename 的形式才可以 push

1
2
3
4
5
6
# Tag the image
docker tag username/repository:tag
docker tag helloworld leon/get-started:part1

# Upload your tagged image to the repository:
docker push leon/get-started:part1

Pull and run the image from the remote repository

1
docker run -p 4000:80 username/repository:tag

儲存映像檔

如果要建立映像檔到本地檔案,可以使用 docker save 命令

1
2
3
4
5
$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              14.04               c4ff7513909d        5 weeks ago         225.4 MB

docker save -o ubuntu_14.04.tar ubuntu:14.04

載入映像檔

可以使用 docker load 從建立的本地檔案中再匯入到本地映像檔庫

1
2
docker load --input ubuntu_14.04.tar
docker load < ubuntu_14.04.tar

進入容器

attach 開啟一個和正在運行的處理序交互的終端,如果該處理序結束,原docker container的處理序也會結束。attach只可以用在以 /bin/bash 命令啟動的容器, 比如 docker run ubuntu /bin/bash

attach 命令有時候並不方便。當多個窗口同時 attach 到同一個容器的時候,所有窗口都會同步顯示。當某個窗口因命令阻塞時,其他窗口也無法執行操作了

exec 可以開啟多個終端實例, exec -i /bin/bash,由此可見exec其實是在運行中的容器中執行一個命令,比如/bin/bash 來達到交互的目的。

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. OS-Ubuntu
FROM Ubuntu

# 2. Update apt repo
RUN apt-get update
# 3. Install denpendencies using apt
RUN apt-get install python

# 4. Install python denpendencies using pip
RUN pip install flask
RUN pip install flask-mysql

# 5. Copy source code to /opt folder
COPY . /opt/source-code

# 6. Run the web server using 'flask' command
ENTRYPOINT FLASK_APP=/opt/source-code/app.py flask run
1
2
3
# 可加 --no-cache,避免在 Build Docker image 時被 cache 住,造成沒有 build 到修改過的 Dockerfile
docker build -t user_name/custom_app Dockerfile
docker push user_name/custom_app

Dockerfile 指令

注意一個映像檔不能超過 127 層

1
2
3
4
5
#一般會建議放置 Dockerfile 的目錄為空目錄。也可以透過 .dockerignore 檔案(每一行新增一條排除模式:exclusion patterns)來讓 Docker 忽略路徑下的目錄和檔案。
docker build -t <repositoryName>:<tagName> .

#這樣也可以,只是不指定名稱
docker build .

FROM

一定要有,只能有一個 FROM,基於某個已存在的 image 進行二次開發。

1
FROM ubuntu:14.04.2

MAINTAINER

指定維護者訊息。

1
MAINTAINER <name | email>

RUN

對映像檔執行相對應的命令。每運行一條 RUN 指令,映像檔就會新增一層,主要用來安裝 packages、設定系統環境等

1
2
3
4
# shell形式
RUN <command> <param1> <param2>
# exec形式
RUN ["executable", "param1", "param2"]

前者將在 shell 終端中運行命令,即 /bin/sh -c;後者則使用 exec 執行。指定使用其它終端可以透過第二種方式實作,例如 RUN ["/bin/bash", "-c", "echo hello"]

每條 RUN 指令將在當前映像檔基底上執行指定命令,並產生新的映像檔。當命令較長時可以使用 \ 來換行。

EXPOSE

設定 Docker 伺服器容器對外的埠號,供外界使用。在啟動容器時需要透過 -P,Docker 會自動分配一個埠號轉發到指定的埠號(當然也可以自己給,例如 -p 1055:80)

1
EXPOSE 80

ADD

該命令將複製指定的 <src> 到容器中的 <dest>。 其中 <src> 可以是 Dockerfile 所在目錄的相對路徑;也可以是一個 URL;還可以是一個 tar 檔案(其複製後會自動解壓縮)。

1
2
ADD <src> <dest>
ADD text.conf /etc/apache2/test.conf

USER

切換使用者身份,Docker 預設使用者是 root,但若不需要,建議切換使用者身份,畢竟 root 權限太大了,使用上有安全的風險。

1
USER Leon

VOLUME

建立一個可以從本地端或其他容器掛載的掛載點,一般用來存放資料庫和需要保存的資料等。

1
2
VOLUME ["/data"]
VOLUME ["/storage1", "/storage2", "/storage2"]

WORKDIR

用來切換工作目錄,Docker 預設工作目錄是在根目錄,只有 RUN 能執行 cd 指令切換目錄,只作用在當下的 RUN,也就是說每一個 RUN 都是獨立進行的。如果想讓其他指令在指定的目錄下執行,就得靠 WORKDIR。WORKDIR 的變更影響是持續的,不用每個指令前都使用一次 WORKDIR

1
2
3
4
5
6
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

#則最終路徑為 /a/b/c

ENV

指定一個環境變數,會被後續 RUN 指令使用,並在容器運行時保持。

1
2
3
4
5
6
ENV <key> <value>

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && 
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ARG

1
格式:ARG <參數名>[=<預設值>]

建構參數和 ENV 的效果一樣,都是設定環境變數。所不同的是,ARG 所設定的建構環境的環境變數,在將來容器運行時是不會存在這些環境變數的。但是不要因此就使用 ARG 保存密碼之類別的訊息,因為 docker history 還是可以看到所有值的。

Dockerfile 中的 ARG 指令是定義參數名稱,以及定義其預設值。 該預設值可以在建構命令

1
docker build 中用 --build-arg <參數名>=<>

來覆蓋。

COPY

能將本機端的檔案或目錄,複製到 image 內。(與 ADD 雷同,差別在於不會做解壓縮,一般都是使用COPY)

1
2
3
#複製本地端的 <src>(為 Dockerfile 所在目錄的相對路徑)到容器中的 <dest>。
COPY <src> <dest>
COPY test.rb ./

ENTRYPOINT

指定 Docker image 運行成 instance (也就是 Docker container) 時,要執行的指令或檔案。在這個範本中,test.rb 就是要執行的檔案。

1
2
3
4
5
6
ENTRYPOINT ["./test.rb"]

# shell形式
ENTRYPOINT ["executable", "param1", "param2"]
# exec形式
ENTRYPOINT command param1 param2

會將 docker run xxx 後面的參數,當作 ENTRYPOINT 指令的參數

1
2
3
ENTRYPOINT ["echo"]
docker run CONTAINER echo foo
# => echo foo

CMD

指定啟動容器時執行的命令,每個 Dockerfile 只能有一條 CMD 命令。如果指定了多條命令,只有最後一條會被執行。

1
2
3
4
5
6
7
8
#在 /bin/sh 中執行,使用在給需要互動的指令

# shell形式
CMD command param1 param2
# exec形式
CMD ["executable","param1","param2"]
#參數形式: 作為 ENTRYPOINT 的預設參數
CMD ["param1","param2"]

會將 docker run xxx 後面的參數,覆蓋掉 CMD 指定的命令。

1
2
3
CMD ["echo"]
docker run CONTAINER echo foo
# => foo

RUN ENTRYPOINT & CMD

shell & exec 形式差別

shell

  • 預設使用 /bin/sh -c 來運行命令,如果鏡像中不包含/bin/sh,容器會無法啟動。

exec (推薦的格式)

  • exec 直接運行指定的指令
  • exec指定的命令不由shell啟動,因此也就無法使用shell中的環境變數,如$HOME。需要環境變數可以指定命令為 shCMD [ "sh", "-c", "echo", "$HOME" ]

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 記得要指定版本,不然很容易因為版本導致這個 Dockerfile 無法使用
FROM ubuntu:14.04

RUN apt-get update -qq && apt-get install -y aptitude git imagemagick curl vim bash-completion htop nodejs mysql-client libmysqlclient-dev

RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
RUN \curl -sSL https://get.rvm.io | bash -s stable
RUN /bin/bash -l -c "rvm install 2.1.5"
RUN /bin/bash -l -c "gem install bundler -v 1.10.5 --no-ri --no-rdoc"

ENV APP_ROOT /project_name

RUN mkdir $APP_ROOT
WORKDIR $APP_ROOT
ADD . $APP_ROOT
RUN /bin/bash -l -c "bundle install"

範例二

slack_neuralyzer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM ubuntu:14.04

MAINTAINER leonji mgleon08@gmail.com

RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get -y install curl

RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
RUN curl -sSL https://get.rvm.io | bash -s stable
RUN /bin/bash -l -c "rvm install 2.4.2"
RUN /bin/bash -l -c "gem install slack_neuralyzer"

CMD /bin/bash -l -c "slack_neuralyzer -t <SLACK_TOKEN> -u <USER_NAME> -D <USER_NAME> -m -e -r 1"
1
2
3
4
5
6
7
8
# 可加上 --no-cache,避免在 Build Docker image 時被 cache 住,而造成沒有 build 到修改過的 Dockerfile。
docker build -t leon:slack
# 放到背景去跑,跑完就結束
docker run -d <imageID>|<imageName>
# 之後會產生 container
docker ps -a
docker start <containerID>
# 就會跑了,而且會連同原本 run 加上的 -d 參數也會一起執行

bin/bash -l,中的 -l 6.1 Invoking Bash

.env file

docker alias

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# stop all running containers
alias dstopall='docker stop $(docker ps --no-trunc -aq)'
# remove all containers
alias drmall='docker rm $(docker ps -a -q)'
# remove temp image
alias drminone='docker rmi $(docker images -f dangling=true -q)'

# or use function

function dstopall() {
    docker stop `docker ps --no-trunc -aq`
}

# remove all containers
function drmall() {
    docker rm `docker ps -a -q`
}

# remove temp image
function drminone() {
    docker rmi `docker images -f dangling=true -q`
}

官方文件:

參考文件:

Comments