上一篇文章對總體結構進行了簡單記錄,這一篇介紹下關於Dockerfile自定義鏡像以及各個服務的配置。
> 其實 `https://hub.docker.com/` 上面各類基礎鏡像很是完善,特別是官方的鏡像質量很是之高,而我再搗騰一次徹底是爲了讓本身掌握 Dockerfile 方面的技能而已。
在選擇基礎鏡像方面,推薦使用 `Alpine` ,而後再它上面進行定製,由於它很是的小僅3M。個人 Nginx/Redis 是在 `Alpine` 基礎上定製的,`PHP` 是在 `CentOS7`上面進行的定製。截圖你們能夠感覺下大小:
![](&&&SFLOCALFILEPATH&&&FE7C23F8-FC2D-4B9A-8CF3-B22471ABC2E2.png)
# Dockerfile 與 Compose 創建關聯
關於概念能夠看這裏:
https://yeasy.gitbooks.io/docker_practice/content/image/build.html
我這裏以 PHP/Redis/Nginx 的定製來進行一些說明(我也只是現學現用,但願高手多指教)。
在上篇的 docker-compose.yml 文件中以下的配置:
```yaml
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 來聊一聊實際如何編寫。
```dockerfile
FROM alpine:3.7
# 解釋信息
LABEL maintainer="HeLei <dayugog@gmail.com>"
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** 的內容就至關於直接執行某個命令。可是當存在時就是另一回事。以上面的爲例:
```docker
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` 腳本內容
```shell
#!/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。若是不是就會直接執行,例如:
```shell
➜ ~/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 已經寫好了,經過下面的命令便可建立鏡像啓動容器。
```shell
➜ ~/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也遇到過, 經過將配置修改成:
```conf
bind 0.0.0.0
```
宿主機可以鏈接到服務器上。這樣設置的含義是,讓容器中的Redis監聽容器ip的全部端口。這樣設置而不是指定ip是由於每一個鏡像能夠啓動多個容器,而每一個容器的ip地址是不肯定的。
2. **鏡像建立時報錯**
報錯信息以下:
```error
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`。而後須要從新建立鏡像。
3. **Nginx 沒法鏈接php-fpm**
這個錯誤其實與宿主機沒法鏈接Redis很像,錯誤信息:
```error
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可正常啓動。
4. **訪問php文件時找不到文件**
執行動態文件時,出現了文件找不到的提示,具體錯誤信息:
```error
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
微信公衆號:
![pub](http://ol59nqr1i.bkt.clouddn.com/mp-qr.jpg)
參考資料:
- https://yeasy.gitbooks.io/docker_practice/content/
- https://docs.lvrui.io/2017/06/09/%E7%BC%96%E5%86%99docker-entrypoint-sh%E5%85%A5%E5%8F%A3%E6%96%87%E4%BB%B6/
- https://pkgs.alpinelinux.org/packages