構建Dockerfile

基礎命令

名稱 做用 示例
docker systen df 查看鏡像、容器、數據卷所佔的空間
docker images -q 產生指定範圍的id列表 docker image ls -q redis
docker image rm $() 批量刪除指定鏡像 docker image rm $(docker image ls -q redis)
docker run 基於鏡像啓動容器 docker run --name webserver -d -p 80:80 nginx
docker exec 進入容器 docker exec -it webserver /bin/bash
docker diff 查看容器存儲層的改動 docker diff webserver
docker commit 將容器的存儲層保存爲鏡像 docker commit [選項] <容器ID或容器名> [<倉庫名>[:<標籤>]]
docker history 具體查看鏡像歷史 docker history nginx:v2
docker export 導出本地容器 docker export c422162c86da >mysql.tar
docker import 導入容器快照到本地鏡像倉庫 cat mysql.tar | docker import - test/mysql:v1.0
docker save 保存鏡像(推薦Docker Registry方式) docker save docker.io/mysql | gzip > mysql-test.tar.gz
docker load 加載鏡像(推薦Docker Registry方式) docker load -i mysql-test.tar.gz

Dockerfile指令

名稱 做用 示例
FROM 指定基礎鏡像 FROM scratch 指定一個空白的鏡像
RUN 用來執行命令行命令的 RUN ["可執行文件", "參數1", "參數2"]
COPY 從構建上下文目錄中 <源路徑> 的文件/目錄複製到新的一層的鏡像內的 <目標路徑> 位置 COPY <源路徑>... <目標路徑>
ADD 更高級的複製文件 (幾乎不用它)全部複製文件用COPY,僅在須要自動解壓縮的場合使用 ADD
CMD 容器啓動命令 推薦使用 exec 格式 CMD ["可執行文件", "參數1", "參數2"...] 注意是雙引號
ENTRYPOINT 入口點 1 讓鏡像變成像命令同樣使用; 2 應用運行前的準備工做
ENV 設置環境變量 ENV <key1>=<value1> <key2>=<value2>...
ARG 構建參數 ARG <參數名>[=<默認值>]
VOLUME 定義匿名卷 VOLUME ["<路徑1>", "<路徑2>"...]
EXPOSE 聲明運行時容器提供服務端口 EXPOSE <端口1> [<端口2>...]
WORKDIR 指定工做目錄 WORKDIR <工做目錄路徑>
USER 指定當前用戶 USER <用戶名>
HEALTHCHECK 健康檢查 HEALTHCHECK [選項] CMD <命令> :設置檢查容器健康情況的命令
ONBUILD 爲他人作嫁衣 ONBUILD <其它指令>

Dockerfile

  • 介紹
Dockerfile 是一個文本文件,裏面包含了一條條的指令,每一條指令構建一層,所以每一條指令的內容都是描述該層如何構建。
  • 使用dockerfile的好處
避免了docker commit方式帶來的臃腫,實際應用中不該採用docker commit.
  • 基本流程
mkdir mynginx
cd mynginx
touch Dockerfile

vim Dockerfile,寫入
FROM nginx
RUN echo '<h1>Hello, Docker!<h1>' > /usr/share/nginx/html/index.html

構建鏡像
docker build -t nginx:v3 .  # 注意最後面有一個點

or docker build - < Dockerfile #從標準輸入中讀取 Dockerfile 進行構建
or cat Dockerfile | docker build -

or docker build http://server/context.tar.gz  #用給定的 tar 壓縮包構建

or docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14 # 直接用 Git repo 進行構建
or docker build - < context.tar.gz # 從標準輸入中讀取上下文壓縮包進行構建

正確構建

Dockerfile 中每個指令都會創建一層, RUN 也不例外。每個 RUN 的行爲,
就和剛纔咱們手工創建鏡像的過程同樣:新創建一層,在其上執行這些命令,執行結束
後, commit 這一層的修改,構成新的鏡像。

錯誤的示例

FROM debian:jessie
     RUN apt-get update
     RUN apt-get install -y gcc libc6-dev make
     RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
     RUN mkdir -p /usr/src/redis
     RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
     RUN make -C /usr/src/redis
     RUN make -C /usr/src/redis install

正確的寫法

FROM debian:jessie
     RUN buildDeps='gcc libc6-dev make' \
        && apt-get update \
        && apt-get install -y $buildDeps \
        && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.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 ./package.json /app/
  這並非要複製執行 docker build 命令所在的目錄下的 package.json ,也不是複製
  Dockerfile 所在目錄下的 package.json ,而是複製 上下文(context) 目錄下的
  package.json

ADD

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...

CMD

shell格式: CMD echo $HOME
實際執行: CMD [ "sh", "-c", "echo $HOME" ]
Docker 不是虛擬機,容器中的應用都應該之前臺執行,而不是像虛擬機、物理機裏面那樣,
用 upstart/systemd 去啓動後臺服務,容器內沒有後臺服務的概念。

CMD service nginx start
這個命令會被理解爲 CMD [ "sh", "-c", "service nginx start"] ,所以主進程其實是 sh 。
那麼當 service nginx start 命令結束後, sh 也就結束了, sh 做爲主進程退出了,天然就會令容器退出。

正確的作法是直接執行 nginx 可執行文件,而且要求之前臺形式運行。好比:
CMD ["nginx", "-g", "daemon off;"]

ENTRYPOINT

  • 場景一:讓鏡像變成像命令同樣使用
  • 錯誤的示範
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]

docker build -t myip .
docker run myip
docker run myip -i # 若是加上-i參數,就會報錯
  • 正確的示範
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
  • 場景二:應用運行前的準備工做
啓動容器就是啓動主進程,但有些時候,啓動主進程前,須要一些準備工做。
官方redis鏡像

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
docker-entrypoint.sh
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi
exec "$@"

ENV

官方node鏡像
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.ta
r.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=
1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
在這裏先定義了環境變量 NODE_VERSION ,其後的 RUN 這層裏,屢次使用 $NODE_VERSION 來進行操做定製。
能夠看到,未來升級鏡像構建版本的時候,只須要更新 7.2.0 便可, Dockerfile 構建維護變得更輕鬆了。
下列指令能夠支持環境變量展開:
ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、 STOPSIGNAL 、 ONBUILD 。

ARG

Dockerfile 中的 ARG 指令是定義參數名稱,以及定義其默認值。該默認值能夠在構建命令
docker build 中用 --build-arg <參數名>=<值> 來覆蓋。

VOLUME

VOLUME /data
這裏的 /data 目錄就會在運行時自動掛載爲匿名卷,任何向 /data 中寫入的信息都不會記錄進容器存儲層,
從而保證了容器存儲層的無狀態化。固然,運行時能夠覆蓋這個掛載設置。
docker run -d -v mydata:/data xxxx
在這行命令中,就使用了 mydata 這個命名卷掛載到了 /data 這個位置,替代了Dockerfile 中定義的匿名卷的掛載配置。

EXPOSE

要將 EXPOSE 和在運行時使用 -p <宿主端口>:<容器端口> 區分開來。 -p ,是映射宿主端口和
容器端口,換句話說,就是將容器的對應端口服務公開給外界訪問,而 EXPOSE 僅僅是聲明
容器打算使用什麼端口而已,並不會自動在宿主進行端口映射

WORKDIR

  • 錯誤的示例
初學者常犯的錯誤是把 Dockerfile 等同於 Shell 腳原本書寫
RUN cd /app
RUN echo "hello" > world.txt

若是須要改變之後各層的工做目錄的位置,那麼應該使用 WORKDIR 指令

USER

USER 指令和 WORKDIR 類似,都是改變環境狀態並影響之後的層,用戶需提早建好.
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

HEALTHCHECK

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
這裏咱們設置了每 5 秒檢查一次(這裏爲了試驗因此間隔很是短,實際應該相對較長),如
果健康檢查命令超過 3 秒沒響應就視爲失敗,而且使用 curl -fs http://localhost/ || exit
1 做爲健康檢查命令。

查看 docker inspect --format '{{json .State.Health}}' web | python -m json.tool

ONBUILD

ONBUILD 是一個特殊的指令,它後面跟的是其它指令,好比 RUN , COPY 等,而這些指令,
在當前鏡像構建時並不會被執行。只有當以當前鏡像爲基礎鏡像,去構建下一級鏡像的時候纔會被執行。
Dockerfile 中的其它指令都是爲了定製當前鏡像而準備的,惟有 ONBUILD 是爲了幫助別人定製本身而準備的。
  • 假設一個node.js的dockerfile以下
FROM node:slim
RUN mkdir /app
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]
  • 如今有其餘的鏡像依賴這個鏡像,假設基礎鏡像叫my-node
FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]
  • 那麼其餘依賴的子項目的鏡像爲
FROM my-node
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
基礎鏡像變化後,各個項目都用這個 Dockerfile 從新構建鏡像,會繼承基礎鏡像的更新
可是問題只解決了一半,若是這個子Dockerfile 裏面有些東西須要調整呢?
好比 npm install 都須要加一些參數,那怎麼辦?這一行 RUN 是不可能放入基礎鏡像的,
由於涉及到了當前項目的 ./package.json ,難道又要一個個修改麼?
因此說,這樣製做基礎鏡像,只解決了原來的 Dockerfile 的前4條指令的變化問題,
然後面三條指令的變化則徹底沒辦法處理。
  • 用ONBUILD重寫基礎鏡像
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

在用ONBUILD構建基礎鏡像的時候,這三行並不會被執行
  • 那麼子鏡像只需
FROM my-node

當在各個項目目錄中,用這個只有一行的 Dockerfile 構建鏡像時,以前基礎鏡像的那三行 ONBUILD 就會開始執行,
成功的將當前項目的代碼複製進鏡像、而且針對本項目執行 npm install ,生成應用鏡像

參考文檔

Docker 從入門到實踐
相關文章
相關標籤/搜索