Dockerfile 與 Compose 環境搭建學習筆記(二)

上一篇文章對整體結構進行了簡單記錄,這一篇介紹下關於Dockerfile自定義鏡像以及各個服務的配置。

其實 https://hub.docker.com/ 上面各種基礎鏡像非常完善,特別是官方的鏡像質量非常之高,而我再搗騰一次完全是爲了讓自己掌握 Dockerfile 方面的技能而已。

在選擇基礎鏡像方面,推薦使用 Alpine ,然後再它上面進行定製,因爲它非常的小僅3M。我的 Nginx/Redis 是在 Alpine 基礎上定製的,PHP 是在 CentOS7上面進行的定製。截圖大家可以感受下大小:

Dockerfile 與 Compose 建立關聯

關於概念可以看這裏:
https://yeasy.gitbooks.io/docker_practice/content/image/build.html

我這裏以 PHP/Redis/Nginx 的定製來進行一些說明(我也只是現學現用,希望高手多指教)。

在上篇的 docker-compose.yml 文件中如下的配置:

dev.nginx.srv:
    image: lei_nginx:1.14.0
    build: ./nginx
    volumes:
        - ./nginx/conf:/home/work/app/nginx/conf
      - ./www:/home/work/www
    ports:
        - "80:8080"
      - "443:443"
    restart: always

這裏重要的是多了 build 這個選項,設置的對應目錄中可以找到 Dockerfile 這個文件,當我們 docker-compose up 時,docker會根據這個文件去先創建鏡像,然後啓動一個容器。

Dockerfile 如何寫

網絡上有非常多關於 Dockerfile 該如何寫的最佳實踐,我覺得有幾點特別重要:
- 一個容器只運行一個進程;
- 鏡像層數儘可能少,當然還需要考慮可讀性等方面的因素;
- RUN指令應該用 \ 分成多行方便閱讀;
- 容器鏡像要儘可能的小。

更多最佳實踐可以看這裏:
https://yeasy.gitbooks.io/docker_practice/content/appendix/best_practices.html

接下來以 Redis 的 Dockerfile 來聊一聊實際如何編寫。

FROM alpine:3.7

# 解釋信息
LABEL maintainer="HeLei <[email protected]>"

ENV REDIS_VERSION=3.2.11 \
    SRC_DIR=/home/work/src \
    DATA_DIR=/home/work/app/redis/data \
    CONF_DIR=/home/work/app/redis/conf

# 設置系統時區
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

COPY src/ $SRC_DIR

# 編譯文件
RUN set -ex; \
    \
    addgroup -S work && adduser -S -G work work; \
    apk add --no-cache --virtual .build-deps \
            coreutils \
            gcc \
            jemalloc-dev \
            linux-headers \
            make \
            musl-dev \
        ; \
    \
    cd $SRC_DIR; \
    tar xvzf redis-$REDIS_VERSION.tar.gz; \
    cd redis-$REDIS_VERSION; \
    make && make install; \
    apk del .build-deps; \
    \
    mkdir -p $DATA_DIR && mkdir -p $CONF_DIR; \
    chown -R work:work /home; \
    rm -rf $SRC_DIR

# 拷貝配置文件
COPY conf/ /home/work/app/redis/conf

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

# 導出端口
EXPOSE 6379
# 啓動redis
CMD ["redis-server"]

第一行 FROM 用來指定基礎鏡像。也就是你要在什麼鏡像上進行定製,我這裏選擇的是 alpine,這是一個提供的基礎空白對象非常小。只是它上面的包管理是 apk ,使用時需要掌握下它的一些參數。

LABEL可以理解成添加一些說明、描述信息。我這裏僅添加了自己的聯繫方式。可以通過反斜線 \ 來進行換行。

ENV用來設置環境變量,例如:定義一些系統版本、路徑的環境變量,在後續RUN中可以使用(當然不僅僅是RUN中可用),也可以用改寫原有的環境變量,例如:PATH。

RUN這是一個非常重要的命令,它是用來執行命令行的命令。就像上面看到的用 yum 安裝更新軟件,make編譯代碼等。可以通過反斜線 \ 來進行換行。

COPY它是將宿主機的內容複製到容器中指定的路徑。

EXPOSE指令用於指定容器將要監聽的端口。一般設置爲應用程序使用常見的端口,例如Redis設置爲:6379

現在重點說下 CMDENTRYPOINT 兩個命令。如果Dockerfile中沒有 ENTRYPOINT 選項,CMD 的內容就相當於直接執行某個命令。但是當存在時就是另外一回事。以上面的爲例:

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

# 啓動redis
CMD ["redis-server"]

這裏設置了一個 ENTRYPOINT ,像上面這種情況的時候如果直接啓動一個容器時,相當於最後應用啓動執行的命令是:./docker-entrypoint.sh redis-server

根據這個特性,docker-entrypoint.sh 內部可以根據相關參數進行特殊處理。來看下我的 docker-entrypoint.sh 腳本內容

#!/bin/sh
set -e

cd `dirname $0`

# 對文件夾進行權限修改
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    chown -R work:work /home
  exec redis-server /home/work/app/redis/conf/redis.conf
fi

exec "$@"

可以看到如果腳本後面帶的參數是redis-server則會先進行相關目錄授權,然後啓動redis。如果不是就會直接執行,例如:

➜ ~/dockerEnv >docker run -it --rm redis:3.2.11 redis-cli -v
redis-cli 3.2.11

會直接執行後面這個命令,你可以看到redis客戶端的版本信息。這也就是表示,可以把鏡像當成一個命令來使用了。

有了 ENTRYPOINT 這個功能,可以用它在服務啓動時,做更多操作 。例如可以結合 docker-compose.yml 中設置的環境變量做更多事情。可以查看官方的MySQL的 docker-entrypoint.sh 文件內容。

依據Dockerfile啓動容器

Dockerfile 已經寫好了,通過下面的命令即可創建鏡像啓動容器。

➜ ~/dockerEnv >docker build -t lei_redis:3.2.11 .

在 redis/ 目錄下執行上面的命令,他會先獲取基礎鏡像,然後根據命令逐條執行,完成redis的編譯、安裝以及相關清理工作。

編譯完成後可用通過docker image ls查看當前的鏡像列表數據。

然後通過 docker run -it -p 6379:6379 -d lei_redis:3.2.11 啓動一個容器。

啓動完成後,大家可以用redis客戶端鏈接查看redis已經正常啓動。

當然還有 PHP/Nginx 的鏡像定製,以及每個服務的配置,大家可以在github上查看詳情,這裏就不再贅述了,剩下再介紹下這個過程中遇的到的幾個錯誤。

遇到的錯誤

  1. 在宿主機中無法連接Redis
    這是由於bind的問題。以前在 vagrant 中安裝redis也遇到過, 通過將配置修改爲:
bind 0.0.0.0

宿主機能夠連接到服務器上。這樣設置的含義是,讓容器中的Redis監聽容器ip的所有端口。這樣設置而不是指定ip是因爲每個鏡像可以啓動多個容器,而每個容器的ip地址是不確定的。

  1. 鏡像創建時報錯
    報錯信息如下:
ERROR: for dockerenv_dev.php-fpm.srv_1  Cannot start service dev.php-fpm.srv: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"docker-entrypoint.sh\": executable file not found in $PATH": unknown

這個問題主要是:我的 docker-entrypoint.sh 文件沒有可執行權限,因此在鏡像創建完後,執行ENTRYPOINT指定的腳本時導致錯誤,解決辦法當然很簡單,直接執行:chmod +x docker-entrypoint.sh。然後需要重新創建鏡像。

  1. Nginx 無法連接php-fpm
    這個錯誤其實與宿主機無法連接Redis很像,錯誤信息:
2018/06/13 11:13:26 [error] 5#0: *8 connect() failed (111: Connection refused) while connecting to upstream, client: 172.18.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://172.18.0.2:9000", host: "localhost"

修改 php-fpm 的監聽地址爲:0.0.0.0:9000,Nginx可正常啓動。

  1. 訪問php文件時找不到文件
    執行動態文件時,出現了文件找不到的提示,具體錯誤信息:
2018/06/13 11:21:20 [error] 5#0: *10 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 172.18.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://172.18.0.2:9000", host: "localhost"

由於Nginx與PHP沒有部署在同一個容器中,相關的項目文件只與Nginx進行了共享,而沒有與PHP的容器進行共享。因此當訪問靜態文件時,Nginx直接在自己的容器中完成操作,而訪問php文件時信息傳到了PHP所在的容器,容器內部無法找到對應的php文件而導致的錯誤。

總結

經過2天的折騰,算是基本把環境搭建起來了。不過還有一些其他問題需要思考該如何進行:

  • 如果我的PHP需要新的擴展,該如何去編譯這個擴展包?
  • 如何去監控docker中的應用的狀態?比如:Redis/Nginx等服務的狀態。

後續會繼續摸索分享自己的經驗。

項目地址:
https://github.com/helei112g/docker-env

微信公衆號:dayuTalk
pub

參考資料:

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