Docker Compose 整合發佈應用相關服務

首先,祝各位新年快樂,萬事如意,雞年大吉。javascript

此次要來講說一個和前端並不太相關的東西——docker compose,一個整合發佈應用的利器。css

若是,你對 docker 有一些耳聞,那麼,你可能知道它是什麼。html

不過,你不瞭解也沒有關係,在做者眼中,docker 就相似於一個沙箱,而你的應用起在這個沙箱裏,不受服務器系統環境的影響,同時也不污染服務器,配置完成以後往服務器部署或移除應用都至關方便。前端

而 compose 就如同它的字面意思組合,它就好像是一個大箱子,能夠把幾個不相關的沙箱給組合起來,變成一個總體,就如同小時候動畫片中變形金剛的合體變身。java

Awesome?

理論知識就沒有什麼比官方文檔更好的了,這裏就不講了,主要來看看如何應用。本文主要包含如下幾個部分:node

若是,你只對前端技術感興趣,那麼,這篇文章可能不適合你。

常言道:一個不懂運維的設計,不是一個好前端。

安裝

Windows 和 Mac 裝了 Docker 以後已經自帶 docker-compose,其餘環境根據 Docker 官網介紹,簡單幾步也能完成安裝。

這裏要提一下,在亞馬遜 aws 上安裝 docker-compose,因爲沒有 root 權限會遇到官網上所提到的 Permission denied 錯誤,加了 sudo 也是沒法直接下載到 /usr/local/bin 目錄下的。

硬來不行,還能夠曲線救國嘛~

先將文件下載到 aws 服務器上,再將文件移動到 /usr/local/bin 目錄就能夠了。

curl -L https://github.com/docker/compose/releases/download/1.9.0/docker-compose-`uname -s`-`uname -m` > docker-compose
sudo chown root docker-compose
sudo mv docker-compose /usr/local/bin
sudo chmod +x /usr/local/bin/docker-compose

驗證是否安裝成功,試試 docker-compose version。若是有輸出版本信息,就說明 docker-compose 已經安裝好了。

docker-compose 雖然安裝好了,但並不必定能用,由於 docker 和 docker-compose 是分開安裝,即便它倆各自運行正常,在一塊兒就不必定合拍了。

那怎麼知道它倆合不合拍?答案很簡單,hello world~

Hello world

在任意的目錄下,建立一個 docker-compose.yml 文件,並添加下面的內容。

version: '2'
services:
  helloworld:
    image: 'hello-world'

而後,在當前目錄下使用 docker-compose up 啓動 docker-compose。

啓動時,如遇到

client and server don't have same version (client : 1.22, server: 1.18)

相似這樣的錯誤,能夠經過設置 docker-compose 的 api 版原本解決。

COMPOSE_API_VERSION=auto

不要嘗試經過一次次安裝不一樣的 docker-compose 版原本解決,你會 ? 的。若是,還遇到

docker.errors.InvalidVersion: inspect_network is not available for version < 1.21

這是 Ubuntu 14.04 LTS 默認的 docker 版本過低引發的,須要升級 docker。然而,在 aws 的服務器上升級 docker 版本時,須要先建立 /etc/apt/sources.list.d/docker.list 文件,並添加

deb https://packages.docker.com/1.12/apt/repo ubuntu-trusty main

再運行

sudo apt-get update && sudo apt-get upgrade docker-engine

就能升級成功。看到?這樣的結果,就表示 docker 和 docker-compose 都安裝成功,並且它倆很搭。

Hello world result

經常使用命令

docker-compose 的命令很簡單,它已經將一些 docker 經常使用關於 image, container & volume 的命令都整合在了一塊兒,使發佈變得極其簡單。好比,以前剛剛提到的 docker-compose up,就相似於 docker build & run,用來建立並啓動 container。

其餘經常使用的命令有:

  • build:構建或從新構建 services

  • config:驗證 docker-compose 配置文件

  • create:建立 services

  • down:與 up 相對,中止並刪除 container, image, volumn 等

  • kill:殺死某個 container

  • logs:查看 container 日誌

  • ps:查看 container 信息

  • restart:重啓 services

  • rm:刪除已經中止的 container

  • start:啓動 services

  • stop:中止 service

  • version:顯示 docker-compose 版本

是否是發現有幾個命令和 docker 的命令同樣?的確,但就如同以前的安裝過程同樣,docker-compose 是依賴於 docker 的,docker 命令更底層。好比 docker-compose ps 這個命令,它只會顯示由 docker-compose 啓動的容器信息,但不包含 docker 啓動的容器信息,相反 docker ps 能夠查看由 docker-compose 啓動的容器信息。

還剩幾個命令沒有列出來,有興趣的童鞋能夠經過 docker-compose help 命令或上官網查看更多信息

光說不練假把式。docker-compose 究竟好很差用,只有用了才知道。

Real world

以前,我的博客的靜態資源一直都是經過 node 提供服務。這的確能夠,但這不是 node 的強項。

專業的事交給專業的人去作。 - by S(ome)B(ody)

這個專業的人就是 nginx。

除此以外,2017 年起水果和古哥都強推 https,升級 https 也是箭在弦上(雖然一直有這個打算,也拖到了如今彡(-_-;)彡)。

因而,程序再也不是原先單一的 node 服務,而是,變成了一系列密切相關的服務。若是,經過基礎的 docker 命令來一個個啓動、中止服務的話,那麼,就須要額外添加一個複雜的腳原本控制。

docker-compose 就是用來處理相似的問題。它能夠作到經過一條命令來控制一個應用相關的一系列服務的啓動、中止等,而且不依賴於機器環境,做到隨時能夠將應用遷移至其餘的機器上發佈。

知道了準備作什麼,先看看最終設計的應用結構和以前的對比。

直接看這張圖可能有點蒙圈,沒事,一點點來看。

docker 到 docker-compose 的轉換

本文一開始就有提到,docker 能夠看作是一個小箱子,而 docker-compose 是一個大箱子用來裝這些小箱子。

那麼,如何將小箱子放入這個大箱子裏哪?

很是簡單!只需告訴 docker-compose 如何啓動你的應用就能夠了,那就先看看原先的啓動命令。

docker run -d -p 80:8080 --name blog

啓動命令中,主要配置了一個端口的映射 -p,以及命名了容器名,用於方便地啓動、中止應用。清楚了這些,那麼改爲 docker-compose 的文件也就垂手可得了。

version: '2'
services:
  node:
    build: .
    container_name: node
    ports:
     - "80:8080"

docker 到 docker-compose 的轉換就這樣完成了,這些更新都不須要修改任何的業務邏輯或者打包配置。

試着使用 docker-compose up -d 啓動服務驗證看看。

啓動正常以後,仍是一步步來,先引入 nginx。

引入 Nginx

Nginx 是一個高性能的 Web 服務器,它具備配置簡單、運行穩定和負載均衡等特色,常被做爲靜態資源服務器。(詳細的 Nginx 信息,請自行查詢資料,這方面本人也不是行家)

Nginx 在 docker hub 上有現成的官方鏡像,直接拿來用就能夠了。

version: '2'
services:
  # ...

  nginx:
    image: nginx:stable
    container_name: nginx
    ports:
      - "80:80"
    restart: always

此時,啓動服務會失敗並報錯,由於 nginx 和原有的 node 容器都綁定到了 80 端口。docker-comopse 各個容器之間是相互獨立的,容器內部的接口相互之間不影響,但對外暴露的接口不能相同,否則就會引發衝突。

從以前的結構圖能夠看到,請求所有由 nginx 接受並轉發到 node 服務,也就是說,node 不直接對外提供服務。那麼,docker-compose 中也就能夠移除 ports 部分(這裏便於測試 node 服務依舊暴露 8080 端口)。

其次,靜態文件是由 node 打包後生成的,也就是說須要將 node 服務中的數據共享給 nginx 服務,這就須要用到 volume(數據卷)。數據卷能夠將數據在宿主機和容器之間、容器和容器之間共享,即便容器被刪除了,數據卷依舊存在。

這裏就須要將服務器上的 nginx 配置文件和 node 構建以後的靜態文件共享給 nginx。

version: '2'

services:
  node:
    build: .
    container_name: node
    # node service port export for test
    ports:
     - "8080:8080"
    volumes:
     - ./log/node:/var/log/node

  nginx:
    image: nginx:stable
    container_name: nginx
    depends_on:
      - node
    volumes:
      - ./config/nginx:/etc/nginx/conf.d:ro
      - ./log/nginx:/var/log/nginx
    volumes_from:
      - node:ro
    ports:
      - "80:80"
    restart: always

volume 是 docker 中至關重要及經常使用的一部分,理解它對使用 docker 解決問題有巨大的幫助。推薦一篇關於 docker volume 的文章,有助於理解 volume。

負載均衡

docker-compose 配置完了,再來看看 nginx 配置。本章一開始有提到 nginx 能夠作負載均衡,那該如何配置哪?

在 nginx 中配置負載均衡至關簡單,只需在 upstream 裏配置一下目標服務器。

然而,這裏就會遇到一個問題。因爲,容器之間是相互獨立的,因而,localhost 便沒法在容器之間相互訪問。不過,由同一 docker-compose 所起的容器之間能夠經過容器名相互訪問,這裏就是

upstream node_server  {
    server node:8080 max_fails=2 fail_timeout=30s;
}

若是要額外再起一個服務,只需在 docker-compose 文件中再啓動一個容器(能夠依賴同一套代碼),並將以前所配的 upstream 中額外多添加一條 server 信息,好比:

upstream node_server  {
    server node:8080 max_fails=2 fail_timeout=30s;
    server node-backup:8080 max_fails=2 fail_timeout=30s;
}

這樣即便一個服務掛了,只要另外一個服務還運行正常,nginx 會將請求轉發給運行正常的服務。一個最簡單的複雜均衡就作好了,全部這些都不須要修改任何功能性的代碼。

知道了 nginx 能夠提供負載均衡,但也不要忘了老朋友 pm2。

pm2 經過命令行參數 -i,或配置文件經過起多個實例來作負載均衡(本人的小博客也是用的這個方式)。

引入 nginx 以後,將全站升級成 https 就垂手可得了,只需在配置文件中標明證書及祕鑰文件的位置就能夠了。接下去,就看看如何生成證書和祕鑰。

使用 Letsencrypt 生成 SSL 證書

獲取 ssl 證書的方式有許多種,有的買域名就送證書,這裏介紹一下用 letsencrypt(現已改名爲 certbot)獲取免費 ssl 證書。

常言道:前人栽樹,後人乘涼。

一樣的,letsencrypt 在 docker hub 上也有現成的鏡像。鏡像有了,剩下的就只需根據不一樣的場景來生成證書。

certbot 支持 5 種生成證書的模式,分別是:apache, nginx, webroot, standalonemanual,分別用於不一樣的場景。這裏 nginx 和 certbot 使用的是不一樣的鏡像,因此選用的模式是 webroot

選定了鏡像和模式,那麼參照 certbot 的文檔就可以簡單地生成證書了。

docker run -it --rm --name certbot \
  -v /letsencrypt/etc/letsencrypt:/etc/letsencrypt \
  -v /letsencrypt/lib/letsencrypt:/var/lib/letsencrypt \
  -v /letsencrypt/challenge:/usr/share/nginx/html \
  -v /var/log/letsencrypt:/var/log/letsencrypt \
  deliverous/certbot \
  certonly --webroot -w /usr/share/nginx/html

須要注意的是,在 webroot 模式下申請證書,須要向 certbot 證實服務器能被訪問。certbot 驗證程序會訪問 web root 目錄(這裏是 /usr/share/nginx/html)來驗證。這裏又要用到以前提到的 volume 將目錄共享給 nginx,讓 nginx 可以訪問到目錄內部的文件。

server {
    listen 80;
    listen [::]:80;

    server_name discipled.me;

    # ...
    
    # letsencrypt challenge file location
    location /.well-known {
        root /usr/share/nginx/html;

        access_log  /var/log/nginx/challenge-access.log  main;
        allow all;
    }
    
    ...
}

修改 nginx 配置以後,別忘重啓 nginx 服務。

docker-compose restart nginx

重啓 nginx 以後,而後再運行上面生成證書的命令就能生成證書了。

ssl 證書生成成功

看到 Congratulations!,證書就生成成功了。

再一次修改 nginx 配置,添加 ssl 證書信息,並監聽 443 端口。

# redirect host http://domain to https://domain
server {
    listen 80;
    listen [::]:80;

    server_name discipled.me;

    # letsencrypt challenge file location
    location /.well-known {
        root /usr/share/nginx/html;

        access_log  /var/log/nginx/challenge-access.log  main;
        allow all;
    }

    location / {
        return 301 https://discipled.me$request_uri;
    }
}

# https://domain server
server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name discipled.me;
    charset utf-8;

    gzip on;
    gzip_types    text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
    root /usr/app/build/client/;

    ssl_certificate /etc/letsencrypt/live/discipled.me/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/discipled.me/privkey.pem;

    location / {
        try_files $uri @node;
    }

    location @node {
        proxy_pass http://node_server;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

重啓 nginx 服務後,訪問網站就能夠看到

小鎖加上,大功告成。

七牛的圖牀用 https 還要實名認證,爲了保護(pa)個(cha)人(shui)隱(biao)私,就暫時用 Github 來救一下急。(誰知道有啥好用的圖牀麻煩推薦一下,像七牛同樣支持 qrsync 用腳本批量上傳的就最好了~先謝過...)

證書更新

letsencrypt 生成的證書有效期是 3 個月,因此,至少 3 個月內須要更新一次證書。

certbot 提供了 renew 命令能夠方便地更新證書,使用 --dry-run 參數能夠驗證證書更新命令是否正確。

docker run -it --rm --name certbot \
  -v /letsencrypt/etc/letsencrypt:/etc/letsencrypt \
  -v /letsencrypt/lib/letsencrypt:/var/lib/letsencrypt \
  -v /letsencrypt/challenge:/usr/share/nginx/html \
  -v /var/log/letsencrypt:/var/log/letsencrypt \
  deliverous/certbot \
  renew --dry-run

一樣,看到 Congratulations 說明證書更新成功了。

因爲,本人每個月都會發布文章並重啓服務,就能夠把證書更新一塊兒交由 docker-compose 管理。(這裏偷了個懶,增長了證書同應用之間的耦合關係,仍是建議你們證書是經過系統定時任務來更新,免得哪天忘更新證書,證書就過時了)。

最後

看一下最終的 docker-compose 配置文件和發佈腳本。

# docker-compose.yml
version: '2'

services:
  node:
    build: .
    image: "blog:${TAG_NAME}"
    container_name: node
    # node service port export for test
    ports:
     - "8080:8080"
    volumes:
     - ./log/node:/var/log/node

  nginx:
    image: nginx:stable
    container_name: nginx
    depends_on:
      - node
      - letsencrypt
    volumes:
      - ./config/nginx:/etc/nginx/conf.d:ro
      - ./letsencrypt/etc/letsencrypt:/etc/letsencrypt
      - ./letsencrypt/lib/letsencrypt:/var/lib/letsencrypt
      - ./letsencrypt/challenge:/usr/share/nginx/html
      - ./log/nginx:/var/log/nginx
    volumes_from:
      - node:ro
    ports:
      - "80:80"
      - "443:443"
    restart: always

  letsencrypt:
    image: deliverous/certbot
    container_name: certbot
    volumes:
      - ./letsencrypt/etc/letsencrypt:/etc/letsencrypt
      - ./letsencrypt/lib/letsencrypt:/var/lib/letsencrypt
      - ./letsencrypt/challenge:/usr/share/nginx/html
      - ./log/letsencrypt:/var/log/letsencrypt
    command: renew

發佈腳本主要用來更新代碼,以及獲取應用版本號。

# deploy.sh
# git operation
git reset HEAD --hard
git fetch
git pull

# TAG_NAME used to set docker image tag
export TAG_NAME=`git tag -l | sort -r | head -n 1`

# docker operation
docker-compose down --volumes

docker-compose up --build -d

其餘配置能夠上 github 查看

一扯彷佛又扯遠了,歡迎提意見和建議,順便再問一下有啥好的圖牀推薦。

相關文章
相關標籤/搜索