1. 程式人生 > >開始使用 Docker

開始使用 Docker

提醒:本文最後更新於 842 天前,文中所描述的資訊可能已發生改變,請謹慎使用。

一年前,我在《開始使用 Vagrant》一文中寫到:使用虛擬化軟體安裝 Linux,有著「統一線下線上環境、不受升級宿主系統的影響、容易備份和恢復」等幾大優點,非常適用於搭建 WEB 開發環境。

但 Vagrant 這種依賴 VirtualBox/VMWare/Parallels Desktop 等軟體虛擬完整作業系統的方案有幾個硬傷,例如佔用大量系統資源、新建或啟動虛擬機器不夠迅速等。Docker 是作業系統級虛擬化,它虛擬出來的環境一般被稱為 Docker 容器,而不是虛擬機器。Docker 容器直接執行在宿主系統的作業系統核心之上,啟動一個新的 Docker 容器能在秒級完成。

由於 Docker 輕量、快速和高效,除了用於搭建開發環境,Docker 容器也非常適合用來部署線上服務。最近我將本部落格程式改用 Docker 部署,你現在看到的頁面正是由 Docker 容器提供服務。本文介紹這一過程。

安裝 Docker

Docker 官方文件詳盡地列出了各個系統下的 Docker 安裝說明,請直接點過去看,本文不做搬運。

對於 Windows/Mac 使用者而言,推薦安裝 Docker for Window/Mac,而不是 Docker Toolbox。前者可以直接利用宿主系統的虛擬化機制,擁有更好的效能;後者需要藉助 VirtualBox 執行的 Linux 虛擬機器。

映象和容器

Docker 基於 Docker 映象執行容器,通常我們所需大部分映象都可以在 hub.docker.com 找到。

在裝好 Docker 的終端中,執行以下命令就可以啟動容器:

docker run ubuntu uname -a

不出意外,可以看到這樣的輸出:

Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
2f0243478e1f: Pull complete
d8909ae88469: Pull complete
820f09abed29: Pull complete
01193a8f3d88: Pull complete
Digest: sha256:8e2324f2288c26e1393b63e680ee7844202391414dbd48497e9a4fd997cd3cbf
Status: Downloaded newer image for ubuntu:latest
Linux 99bebffc2678 4.4.16-moby #1 SMP Tue Aug 9 17:20:17 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

docker run 命令用來從指定映象啟動容器。由於我本地沒有 ubuntu 映象,Docker 首先會從官方 Hub 下載它;然後啟動容器並執行 uname -a 命令。這個命令是在 Docker 容器內執行,輸出的是容器系統資訊。

檢視和管理 Docker 映象及容器,主要有這些命令:

  • docker images:檢視本地已經存在的映象,-a 列出所有(預設不包括中間映象);
  • docker rmi IMAGE:刪除指定的映象,-f 強制刪除;
  • docker ps:檢視執行中的 Docker 容器,-a 列出所有(預設不包括未執行的容器);
  • docker rm CONTAINER:刪除指定的容器,-f 強制刪除;

使用 Docker 的最佳實踐是保持職責單一,一個容器只提供一個服務。我的部落格主要有這些服務:

  • Nginx(80/443);
  • MySQL(3306);
  • Memcached(11211);
  • ElasticSearch(9200);
  • ThinkJS(8085);

考慮到我經常折騰 Nginx,我選擇把它留在宿主系統,剩餘四個服務則改用 Docker 容器來執行。

構建映象

我需要的 Mysql、Memcache、ElasticSearch 容器都可以使用官方映象來執行。但我的部落格系統,使用官方 Node.js 映象存在兩個問題:1)官方映象中的 npm 是 v2,我希望換成 v3;2)官方映象沒有 libvips 庫,無法安裝本部落格程式所依賴的 sharp npm 包。

遇到這種情況,可以在 Docker Hub 看看有無第三方 Docker 映象能夠滿足需求,也可以構造自己的映象。我選擇後者。

要構建自己的 Docker 映象,一般都會選定一個已有的映象做為基礎,再在上面增加自己的修改。我的 DockerFile 如下:

FROM marcbachmann/libvips
MAINTAINER [email protected]

RUN apt-get update

# 修改時區
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime

# 安裝依賴
RUN apt-get install -y \
  python \
  curl \
  build-essential

# 安裝 Node.js v4.x.x LTS
RUN curl -sL https://deb.nodesource.com/setup_4.x | bash -
RUN apt-get install -y nodejs

# 安裝 npm v3 和 pm2
RUN npm install -g [email protected] pm2

# 解決 npm 在 docker 下經常 rename 失敗的問題。詳見:
# https://forums.docker.com/t/npm-install-doesnt-complete-inside-docker-container/12640/3
RUN cd $(npm root -g)/npm \
  && npm install fs-extra \
  && sed -i -e s/graceful-fs/fs-extra/ -e s/fs\.rename/fs.move/ ./lib/utils/rename.js

這份 DockerFile 作用是在 marcbachmann/libvips 映象上增加了我需要的 Node.js,將 npm 升級到了 v3,還安裝了 pm2。

在 DockerFile 所在目錄,執行以下命令就可以構建映象,並將其推送至 Docker Hub(這裡略過註冊和登入過程):

docker build -t qgy18/node .
docker push qgy18/node

Docker Compose

Docker Compose 是一個小工具。我們可以在一個檔案裡定義多個容器,使用 docker-compose 命令讓它們全部執行就緒。Docker Compose 非常適合用來部署 WEB 系統這種需要多個容器配合工作的服務。

如果你使用的是 Docker for Windows/Mac,docker-compose 命令應該直接可用。對於 Linux 平臺,請參考官方文件安裝 Docker Compose。

當前,我的部落格系統目錄結構如下:

├── blog
│   ├── app
│   ├── node_modules
│   ├── package.json
│   ├── pm2.json
│   ├── view
│   └── www
├── db
│   ├── ...
│   ├── ququ_blog
│   └── sys
├── docker-compose.yml
├── esroot
│   ├── config
│   ├── data
│   └── plugins
└── shell
    ├── backup_blog_database.sh
    └── install_blog_package.sh

我將所有需要持久化儲存的檔案都放在了宿主系統,例如程式碼目錄(blog),資料庫檔案(db),ElasticSearch 配置、外掛及資料檔案(esroot)。這樣資料更加安全,也更易於管理。

shell 目錄下的 install_blog_package.sh 用來安裝部落格 npm 依賴,我的宿主系統沒有安裝 Node.js,執行 npm install 也需要藉助 Docker 容器,一行命令搞定:

docker run -it --rm -v "$PWD/../blog":/app -w "/app" qgy18/node npm install --registry=http://registry.npm.taobao.org --production

這行命令首先基於前面構建好的映象運行了一個擁有 Node.js 和 npm3 的容器;然後將宿主系統的 blog 目錄對映為容器的 /app 目錄;再將容器的工作目錄設定為 /app;最後執行 npm install 安裝依賴。最為神奇的是,由於指定了 --rm 引數,這個容器在完成工作之後就會被徹底銷燬,不留任何痕跡。

類似的,由於宿主系統不再需要安裝 MySQL,備份資料庫也需要在容器內完成,這時候可以使用 docker exec 命令在已經執行的容器內執行指令。以下是 backup_blog_database.sh 檔案的內容:

docker exec imququ_db mysqldump -uroot -p****** ququ_blog | gzip > ../backup/ququblog.`date +%H`.sql.gz

docker-compose.yml 檔案內容如下,它定義了每個容器基於什麼映象執行,對映哪些目錄,開放哪些埠:

version: '2'
services:
  es:
    image: elasticsearch:2.3.0
    container_name: imququ_es
    volumes:
      - ./esroot/data/:/usr/share/elasticsearch/data
      - ./esroot/config/:/usr/share/elasticsearch/config
      - ./esroot/plugins/:/usr/share/elasticsearch/plugins
    restart: always
    expose:
      - "9200"

  cache:
    image: memcached:1.4.29
    container_name: imququ_cache
    restart: always
    expose:
      - "11211"

  db:
    image: mysql:5.7.14
    container_name: imququ_db
    volumes:
      - "./db:/var/lib/mysql"
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ******
    expose:
      - "3306"
    ports:
      - "127.0.0.1:3306:3306"

  blog:
    depends_on:
      - es
      - cache
      - db
    image: qgy18/node
    container_name: imququ_blog
    volumes:
      - ./blog:/app
    restart: always
    working_dir:
      /app
    entrypoint:
      - pm2
      - start
      - pm2.json
      - --no-daemon
    links:
      - es:es
      - cache:cache
      - db:db
    ports:
      - "127.0.0.1:8085:8085"

在 blog 容器中,我通過 links 配置連線了前面幾個容器。這樣在程式碼中,就可以使用 es 做為 HOST 連線到 Elasticsearch 容器,使用 db 做為 HOST 連線到 MySQL,依此類推。

我定義了 db 容器的 ports 配置,將宿主系統的 3306 埠對映到了 db 容器內,這樣我就可以在宿主系統管理 MySQL 服務。同樣地,使用宿主系統的 8085 埠可以訪問到 blog 容器提供的 WEB 服務。

通過 docker-compose up -d 命令就可以在後臺啟動所有容器。docker ps 可以用來檢視各個容器的執行狀態:

IMAGE                 COMMAND                  PORTS                      NAMES
qgy18/node            "pm2 start pm2.json -"   127.0.0.1:8085->8085/tcp   imququ_blog
elasticsearch:2.3.0   "/docker-entrypoint.s"   9200/tcp, 9300/tcp         imququ_es
mysql:5.7.14          "docker-entrypoint.sh"   127.0.0.1:3306->3306/tcp   imququ_db
memcached:1.4.29      "docker-entrypoint.sh"   11211/tcp                  imququ_cache

本部落格基於 Docker 容器運行了將近一週,非常穩定。宿主系統整體資源佔用跟之前相比,也沒有明顯變化。前幾天我把宿主系統升級到了 Ubuntu 16.04.1 LTS,部落格服務沒受任何影響,這種體驗實在是美妙。

--EOF--

提醒:本文最後更新於 842 天前,文中所描述的資訊可能已發生改變,請謹慎使用。