給 PHP 開發者的 Docker 文件（四）
Tags: php, docker
Published: 2022-02-17 09:21:34

 （本文譯自〈[Docker for PHP Developers](https://www.newmediacampaigns.com/blog/docker-for-php-developers)〉）

## 來建個應用（Application）吧

說了一堆有的沒的（jibber-jibber），來搭個有 Nginx 網頁伺服器和與其通訊的 MySQL 資料庫的應用（application）吧！這不會是複雜到超出想像的應用，但是是個絕佳的機會來學習：

- 如何組建 Docker 映像
- 如何實例化（instantiate）Docker 容器（container）
- 如何用 Docker 卷宗（volume）保存（persist）資料
- 如何聚合（aggregate）容器輸出的紀錄檔（log）
- 如何利用 Docker Compose 管理互相關聯的容器

首先，在你的電腦內建個專案目錄並切換到該目錄內，本文所有的指令都在這目錄之內下達。

### 遊戲計畫

在做之前，先讓我們規劃一下該怎麼玩，首先我們需要安裝 Docker，然後我們得決定（figure out）這個應用要怎麼拆解成不同的容器。這是個簡單的應用：有一個 Nginx 網頁伺服器、一個 PHP-FPM 應用伺服器、與一個 MySQL 資料庫伺服器，因此我們的這個應用需要三個 Docker 容器，分別從三個 Docker 映像實例化而來。我們也需要決定是否要自建 Docker 映像，還是從 Docker Hub 抓既有的來用就好。

我們也得決定用哪個基礎映像（base image）來作為共用的映像，在此所有的 Docker 映像都將擴展（extend）自該基礎映像。如果我們的 Docker 容器都擴展自同一個基礎映像，那麼可以節省磁碟空間並簡化 Docker 映像的相依（dependency）數量。在本教學中我們將以 [Ubuntu 14.04](https://registry.hub.docker.com/_/ubuntu) 作為我們的基礎映像。

在建構或下載需要的 Docker 映像後，我們將會進行實例化並使用 [Docker Compose](https://docs.docker.com/compose/) 運行我們的應用的 Docker 容器。如果一切按照計畫，我們將會運行（up）起一個可依需求（on-demand）運用的 PHP 開發環境，而這僅僅只須 Bash 指令與幾秒的時間。

最後我們會討論如何聚合容器輸出的紀錄檔（log），記住，容器是免洗的（expendable），而且我們絕不可把資料存在容器內，相反地，我們將把紀錄檔重導（redirect）到各自容器內的標準輸出（standard output）與標準錯誤（standard error）檔案描述子（descriptor），如此一來 Docker Compose 即可收集並管理容器內的紀錄資料。

### 安裝 Docker

Docker 需要 Linux，並且它支援許多的 Linux 發行版：Ubuntu、Debian、CentOS、CRUX、Fedora、Gentoo、RedHat、與 SUSE，挑一個吧！（take your pick）。你的本地的 Linux 作業系統就是 Docker 母機（host），實例化以及運行 Docker 容器在其內。

許多人使用 Mac OS X或是 Windows，這不表示你完蛋了（out of luck），因為有個叫做 [Boot2Docker](https://github.com/boot2docker) 的東西，這工具可以建立一個微型的 Linux 虛擬機，並且這個虛擬機將會被做為 Docker 母機，而非你的本地的作業系統。別擔心，這個虛擬機**真的超小**而且開機只要約 5 秒。

裝好 Boot2Docker 後，可以雙擊 Boot2Docker 應用圖示，或者在終端機（terminal）執行這些 Bash 指令：

```sh
boot2docker init
boot2docker up
eval "$(boot2docker shellinit)"
```

這三行指令會建立虛擬機（假使尚未有建立好了的虛擬機的話），啟動虛擬機，並輸出必要的環境變數（environment variables），如此你的本地作業系統就可以與 Docker 母機相互通訊。

如果你像我一樣不喜歡打字的話，你可以建立 Bash 替身（alias）。加入下面幾行到你的 ~/.bash_profile 檔案內：

```sh
alias dockup="boot2docker init && boot2docker up && eval \"\$(boot2docker shellinit)\""
boot2docker up
eval "$(boot2docker shellinit)"
```
這會建立一個叫做 `dockup`（Docker up 的縮寫）的 Bash 替身，現在只要打簡單的 dockup 在新的終端機就可以建立、啟動、初始化你的 Docker 母虛擬機。

### Nginx Docker 映像

首先我們來關注 Nginx 網頁伺服器，雖然可以確定在 Docker Hub 一定找得到自 Ubuntu 14.04 基礎映像擴展而來的而且合用的映像，但我們會利用這次機會來學習建構自己的 Docker 映像。建立目錄 images/nginx/，新增 Dockerfile 與 start.sh 到那個目錄內，此時你的專案目錄看起來應該是這樣：

```
images/
  nginx/
    Dockerfile
    start.sh
```

用你常用的編輯器打開 Dockerfile 並加入這些內容：

```dockerfile
FROM phusion/baseimage
MAINTAINER YOUR NAME <YOUR EMAIL>
 
CMD ["/sbin/my_init"]
RUN apt-get update && apt-get install -y python-software-properties
RUN add-apt-repository ppa:nginx/stable
RUN apt-get update && apt-get install -y nginx
 
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log
 
RUN mkdir -p /etc/service/nginx
ADD start.sh /etc/service/nginx/run
RUN chmod +x /etc/service/nginx/run
 
EXPOSE 80
 
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
```

利用定義在 [Docker 文件](https://docs.docker.com/engine/reference/builder/)內的指令寫入 Dockerfile 內以建構一個新的 Docker 映像。讓我們一行一行的看它們的作用分別是什麼。

- **Line 1** 以 `FROM` 開頭，用於標示父 Docker 映像的名稱，由該父映像擴展出我們的新映像。我們拿 [phusion/baseimage](https://github.com/phusion/baseimage-docker) 來擴展，因為它提供簡易的 Docker 容器操作工具。
- **Line 2** 以 `MAINTAINER` 開頭，用於標示你自己的名稱與電郵信箱。如果你把這個 Docker 映像分享到 Docker Hub，其他開發者就能夠知道是誰做出這個映像以及有問題要問誰。
- **Line 3** 用於初始化 phusion/baseimage 基礎映像內建的家管（house-keeping）程序。
- **Line 4 - 6** 用於從 Nginx 社群 PPA（personal package archive）安裝最新的穩定版的 Nginx。這個 PPA 提供最新的穩定的 Nginx，讓我們可以迅速地裝起來而不用從源碼編起。
- **ine 7 - 9** 用於更新 Nginx 組態檔案，讓 Nginx 不要以常駐服務（daemonized）模式運作。這幾行也把 Nginx 的存取與錯誤紀錄連結（symlink）到容器的標準輸出與標準錯描述子，如此 Docker 就可以整合（aggregate）管理我們的應用的紀錄資料。我們從不保存（persist）任何資料在容器自身內。
- **Line 10 - 12** 用於拷貝 start.sh 檔案進容器，phusion/baseimage 基礎映像會呼叫這個檔案來啟動 Nginx 伺服器程序。
- **Line 13** 用於告訴 Docker 把所有實例化（instantiated）自這個映像的容器的 80 埠開放（expose）。我們需要開放 80 埠好讓連入（inbound）的HTTP要求（request）能被 Nginx 接收並做適當處理。
- **Line 15** 用於執行 phusion/baseimage 基礎映像提供的家管程序。

接下來，開啟 start.sh 檔案並在最後加入這些內容：

```sh
#!/usr/bin/env bash
service nginx start
```

這裡面的 Bash 指令用於啟動 Nginx 網頁伺服器程序。現在我們已經準備好建構我們的 Docker 映像了，移動到專案目錄 images/nginx/ 內並執行這個 Bash 指令：

```sh
docker build -t tutorial/nginx .
```

![docker build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qwx7n5s8rfsyflwisd4c.png)

根據先前的 Dockerfile 的內容，Docker 開始建構你的 Nginx Docker 映像，並且你會在終端機看到一些輸出內容。你也會看到 Docker 會從 Docker Hub 抓下來任何有相依性（dependency）的父映像（parent image）。完成後，你可以執行 docker images 指令，會輸出現有的 Docker 映像清單，你應該會看到 tutorial/nginx 在清單內。

![docker images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y76u8fay71d8gt34vm7n.png)

恭喜啦！你搞定了你的第一個 Docker 映像。但請記得，這還只是個 Docker 映像，還得利用它去實例化出 Docker 容器，但在做實例化前，讓我們先把 PHP-FPM 和 MySQL 的 Docker 映像都搞定。

### PHP-FPM Docker映像

接下來我們來看看 PHP-FPM，我們將不會自行建構這個 Docker 映像，相反地，我已經在 Docker Hub 分享了一個 nmcteam/php56 的映像，執行下面的 Bash 指令來把這個 PHP-FPM Docker 映像從 Docker Hub 抓回來。

```sh
docker pull nmcteam/php56
```

### MySQL Docker 映像

最後咱們來關注 MySQL，我在 Docker Hub 找到了擴展自 Ubuntu 14.04 基礎映像的 [sameersbn/mysql](https://registry.hub.docker.com/r/sameersbn/mysql/)，執行下面的 Bash 指令來把這個 MySQL Docker 映像從 Docker Hub 抓回來。

```sh
docker pull sameersbn/mysql
```

（待續…）