上一篇文章對整體結構進行了簡單記錄,這一篇介紹下關於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
現在重點說下 CMD 與 ENTRYPOINT 兩個命令。如果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上查看詳情,這裏就不再贅述了,剩下再介紹下這個過程中遇的到的幾個錯誤。
遇到的錯誤
- 在宿主機中無法連接Redis
這是由於bind的問題。以前在 vagrant 中安裝redis也遇到過, 通過將配置修改爲:
bind 0.0.0.0
宿主機能夠連接到服務器上。這樣設置的含義是,讓容器中的Redis監聽容器ip的所有端口。這樣設置而不是指定ip是因爲每個鏡像可以啓動多個容器,而每個容器的ip地址是不確定的。
- 鏡像創建時報錯
報錯信息如下:
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
。然後需要重新創建鏡像。
- 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可正常啓動。
- 訪問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
參考資料: