Background

這篇文章主要紀錄如何在 Docker 中使用 Let’s Encrypt 來完成 Https 的設定。比較偷懶一點就拿別人寫好的程式(Docker 方便的地方)來跟我們之前的專案 做整合。雖然用了別人的工具但其實還是很麻煩啦,還要整合 qa, stg, prd還是得費一番功夫的,那我們開始吧。

Start

回顧一下之前 flask-vue-crud 的資料夾結構:

.
├── appspec.yml
├── client
│   ├── babel.config.js
│   ├── dist
│   ├── Dockerfile
│   ├── package.json
│   ├── package-lock.json
│   ├── public
│   ├── README.md
│   ├── src
│   └── vue.config.js
├── docker-compose.dev.yml
├── docker-compose.yml
├── LICENSE
├── nginx
│   ├── conf.d
│   │   ├── app.dev.conf
│   │   └── app.qa.conf
│   ├── Dockerfile
│   └── Dockerfile.dev
├── README.md
├── scripts
│   ├── after-install.sh
│   └── before-install.sh
└── server
    ├── config.py
    ├── Dockerfile
    ├── locust
    ├── README.md
    ├── requirements.txt
    ├── run.py
    ├── src
    └── tests

我們主要會專注在 nginx 資料夾,同時搭配 docker-compose.yml

  • conf.d: 放置整個專案會用到的 nginx config 的地方,每個 site 會各自有個別的 config,像範例中我放了 dev & qa 兩個不同的 config。
  • Dockerfile: for https 需求所用的 Dockerfile 檔案。
  • Dockerfile.dev: 因為 dev 環境不需要用到 https,所以用到的 images 跟上面不一樣。

那因為我們有兩個 Dockerfile 檔案, Dockerfile & Dockerfile.dev,所以也會對應到兩個 docker-compose 檔案 docker-compose.dev.yml & docker-compose.yml。那因為本章主要介紹如何取得 https 的憑證,所以主要專注在 docker-compose.yml & Dockerfile 上。

.env

從 github clone 檔案下來之後先 checkout 到 https 的 branch,接的先設定環境檔。DOCKER_REGISTRY 我是用 AWS ECR,這邊你可以改成你自己習慣的 DOCKER REGISTRY,BRANCH的部分則設定成 qa

$ git clone https://github.com/TaikerLiang/flask-vue-crud.git
$ cd flask-vue-crud
$ vim .env

.env

DOCKER_REGISTRY=xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/flask-vue-crud
BRANCH=qa

Docker-Compose

先切換一下 Branch 接著在解釋一些程式檔案。

$ git checkout https

Docker-compose 的部份我們是參考 https://github.com/staticfloat/docker-nginx-certbot 中的範例所設置的。environment 中填入你的 email,這是為了申請 SSL 憑證所需要的。args->ENV 的部分我們則抓取 .env中的 BRANCH 值來決定我們之後會抓哪個 nginx 的檔案。

version: '3'
services:
  nginx:
    image: ${DOCKER_REGISTRY}/nginx:${BRANCH}
    restart: always
    build:
      dockerfile: Dockerfile
      context: ./nginx
      args:
        ENV: ${BRANCH}
    environment:
      CERTBOT_EMAIL: "[email protected]"
    ports:
      - "80:80"
      - "443:443"
  api:
    image: ${DOCKER_REGISTRY}/server:${BRANCH}
    build:
      dockerfile: Dockerfile
      context: ./server
      args:
        ENV: ${BRANCH}
  client:
    image: ${DOCKER_REGISTRY}/client:${BRANCH}
    build:
      dockerfile: Dockerfile
      context: ./client
      args:
        ENV: ${BRANCH}

Dockerfile

這邊蠻單純的主要就是根據 ENV 的變數來複製相對應的 conf 檔到 container 中。

FROM staticfloat/nginx-certbot

ARG ENV

COPY ./conf.d/app.${ENV}.conf /etc/nginx/conf.d/default.conf

Nginx Config

在 Nginx 中我們兩個服務 fvc.taiker.space & api.fvc.taiker.space 把出現這兩個網址的地方都換成你自己的網址。

upstream vue {
  server client:8080;
}

server {
    listen              443 ssl;
    server_name         fvc.taiker.space;
    ssl_certificate     /etc/letsencrypt/live/fvc.taiker.space/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/fvc.taiker.space/privkey.pem;

    location / {
        proxy_pass         http://vue;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

server {
    listen              443 ssl;
    server_name         api.fvc.taiker.space;
    ssl_certificate     /etc/letsencrypt/live/api.fvc.taiker.space/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.fvc.taiker.space/privkey.pem;

    location / {

        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

        proxy_pass http://api:8000;
        proxy_redirect   off;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

client

https://api.fvc.taiker.space 換成你自己設定的 URL。

client/src/config.js

let config;

console.log(process.env);

if (process.env.VUE_APP_ENV === "dev") {
  config = {
    $api_url: "http://localhost:5000",
    timeoutDuration: 30000,
  };
} else {
  config = {
    $api_url: "https://api.fvc.taiker.space",
    timeoutDuration: 1000,
  };
}

export { config }

Server

因為我們目前是 qa site,所以在 server/config.py 中的 TestingConfig 裡的 SQLALCHEMY_DATABASE_URI 必須填入正確的位址才不會導致 500 的 error。

server/config.py

class TestingConfig(Config):
    TESTING = True
    DEBUG = True
    DOMAIN = "https://fvc.taiker.space"
    BRANCH = "qa"
    SQLALCHEMY_DATABASE_URI = "YOUT_DB_INFO"

Run

$ docker-compose up --build

接著我們打開瀏覽器輸入 https://fvc.taiker.space/ 可以得到以下結果,可以看到有https字樣。

Screen-Shot-2019-08-31-at-4.08.23-PM

Other

以下是我在測試的過程中遇到的問題,順便列一下給有遇到相同問題的人:

  • Invalid Host header error!: 看網路上解釋是 webpack 會自動檢查你的 Host 如果不一致的話就會出現這個Error。那因為我不熟所以選擇直接把它關掉。增加下面的檔案來關閉:

client/vue.config.js

module.exports = {
    // options...
    devServer: {
        disableHostCheck: true
    }
}
  • 如果遇到連不到網站的情況下 記得把 image 全部刪掉然後重新 rebuild 試看看。
  • SSL 憑證單一網址一週有50次的限制

Reference