別ドメインのWebページのホストをdockerの力でEC2インスタンス1つに統合したときの話

このエントリは[学生LT Advent Calendar 2019](https://qiita.com/advent-calendar/2019/student-lt) 25日目(最後)の記事です。

最後がこんなド初心者でいいのか分からない、誕生日なので許しておくれ

本エントリでは、いろんな辛さからWebページの配信をOS直下のnginxからdockerのnginxとかに移設した話をします。長いかも。

# 背景

1年以上前にドメインを取得して以降、用途に合わせて以下の2つのドメインにコンテンツを追加しています。

+ `huequica.xyz` ブログ兼ポートフォリオ
+ `works.huequica.xyz` 作ったおもちゃを動かす場所

この2つのドメインにコンテンツを配信するのに今までGitHub Pagesと自前で立てたEC2インスタンスに分断して配信を行っていました。
以下の図がわかりやすいかと思います。
![1.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320738/6279f5ce-4bf7-3524-228b-323cad73d3e8.png)

さて、これを運用してきて半年以上が経過したところで幾つかのつらい事案を抱えることになりました。

# なにがつらい？

以下の事情があり、けっこう悩みのタネになっていました。

### GitHub Pages、単純にアップロードがしんどい
Jekyllでの生成、運用をしていた時期がありましたがデザインが気に入らなかったので自分ですべて生成しています。

そのため手元でコンテンツをBuildして、それをリポジトリに突っ込んでpushしてようやく公開される形になっていますが、ファイルパスが大文字小文字を識別していたり？とかで404が帰ってくることがあったりしてつらかったです。

最大の問題は **pushする前の事前チェックができない** ことで、pushしてはアクセスして確認みたいな地獄みたいなことをしていたので大変つらいです。マジで。

### EC2インスタンスの環境が再現しづらい
某PHP製のWebアプリを開発した際、デプロイにVPS的なものが必要だった為とりあえずEC2インスタンスを取得していましたが、環境の不揃いが原因のバグが発生したりした為これもつらさの原因になっていました。
アプリケーションのビジネスロジックの問題ではなく権限エラーが大半のためFixがしづらく余計にしんどかったです。

### 証明書の更新の自動化
調べるとCronでスクリプトを定期で回して更新させるパターンが多いみたいですが、手元で動かすのに失敗していたままほったらかしになっていた為手動更新をしていました。

おまけに毎回コマンドを忘れているので調べる手間もかかってダルい。これもつらかったです。

-------

といった感じで環境を見直すには十分だろう、ということで以下の構成に変更しました。

# おニュー環境
dockerであればMacやWindowsであっても簡単に環境を揃えることができ、また新しくドメインを増やす形でOSSのSaaSサービスを展開することも簡単にできるようになります。そのため今回docker、及び管理を楽にするためにdocker-composeを使いました。

構成は超短的に表すと以下になります。

![2.002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320738/bbd45b66-3ba6-6bf4-f871-b846c5562b9b.png)



今回の場合`huequica.xyz` と `works.huequica.xyz` の2つのドメインの行き先をEC2の1つに集中させ、URLで識別してdockerコンテナの中のnginxに流しています。


ただし、dockerの中にはHTTPアクセスを識別して仕分ける(リバースプロキシ)みたいな機能は無いのでそっちは別で用意することになります。ここで出てくるのが **nginx-proxy**です。


# nginx-proxy

[![jwilder/nginx-proxy - GitHub](https://gh-card.dev/repos/jwilder/nginx-proxy.svg)](https://github.com/jwilder/nginx-proxy)

`nginx-proxy` はリバースプロキシの仕組みを超簡単に導入するためのdockerイメージです。
ここからは実際の`docker-compose.yml`と図を交えて説明します。


# 実際のコンテナ構成
だいたいで図にすると以下になります。
![h.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320738/06229419-e486-c655-f347-82faff475ca3.png)

まずはHTTPの仕分け役をしている`proxy`のymlから見ていきます。


```proxy/docker-compose.yml
version: "3.5"

services:
  nginx-proxy:
    container_name: nginx-proxy
    image: jwilder/nginx-proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      # nginx-proxy-companionに共有させる
      - certs:/etc/nginx/certs:ro
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
    networks:
      - proxy-network
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"

  # SSL証明書発行しないのであれば不要
  letsencrypt-nginx:
    container_name: letsencrypt-nginx
    image: jrcs/letsencrypt-nginx-proxy-companion
    privileged: true
    depends_on: 
      - nginx-proxy
    volumes:
      - certs:/etc/nginx/certs:rw
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: always

volumes:
  certs:
  vhost:
  html:

networks:
  proxy-network:
    name: proxy_network
```

キモになるのはこの3つです。

+ `networks` の項目で他のディレクトリのdocker-composeとの連携場所を作る
+ `nginx-proxy`には上述のネットワークに所属させる
+ SSL証明書を発行する場合は `volumes` を共有させる(していないと警告が出る)


----------

次に`portfolio`のymlです。

```portfolio/docker-compose.yml
version: "3.5"

services:
  app:
    container_name: portfolio_nginx
    image: nginx
    volumes:
      - ./content:/usr/share/nginx/html
      - ./conf.d:/etc/nginx/conf.d
      - ./nginx.conf:/etc/nginx/nginx.conf
    environment:
      - VIRTUAL_HOST=huequica.xyz
      - LETSENCRYPT_HOST=huequica.xyz
      - LETSENCRYPT_EMAIL=hoge@dogemail.com
    networks:
      - proxy-network

networks:
  proxy-network:
    name: proxy_network
```
`environment`の項目で`VIRTUAL_HOST`という見慣れない変数を作っていますが、これが一番大事です。
具体的には、前述の`nginx-proxy`が拾ったHTTPは`VIRTUAL_HOST`のアドレスを元に制御してレスポンスを中継します。そのため、ローカルで稼働させる場合は`localhost`に書き換えるなどしないとなりません。

また、先程の`proxy`内でSSL証明書用のコンテナを入れている場合は`LETSENCRYPT_HOST`と`LETSENCRYPT_EMAIL`を追加するとSSL証明書の取得、定期更新を**自動で**行います。

あとは`proxy`で設定したnginx-proxyのいるネットワークに、コンテンツ配信用のnginxコンテナを所属させることも必要になります。

-------
さきほどとあんまり変わりませんが、`works`の方のymlも掲載しておきます。

```works/docker-compose.yml
version: "3.5"

services:
  works_nginx:
    container_name: works_nginx
    image: nginx
    volumes:
      - ./content:/usr/share/nginx/html
      - ./conf.d:/etc/nginx/conf.d
      - ./nginx.conf:/etc/nginx/nginx.conf
    environment:
      - VIRTUAL_HOST=works.huequica.xyz
      - LETSENCRYPT_HOST=works.huequica.xyz
      - LETSENCRYPT_EMAIL=hoge@dogemail.com
    networks:
      - proxy-network

networks:
  proxy-network:
    name: proxy_network
```

worksもやることは同じです。 `VIRTUAL_HOST`と`LETSENCRYPT_HOST`にドメインを設定して、`LETSENCRYPT_EMAIL`に更新用のメールアドレスを渡してやるだけです。

ただし、PHPやRubyのコンテナと連携して動的なものを返そうと考えている方は**そのコンテナとnginxコンテナのみをつなぐ為のネットワークの新設、所属**が必要になるのでそれだけ注意してください。


# これによって解決したこと
+ 事前のコンテンツチェックができるようになった
+ 証明書が自動更新になり、定期更新など何も気にしなくて良くなった
+ 環境の統一がとれたことにより、デバッグがすこぶる楽になった
+ GitLabなどを自前で動かすこともできるようになった

などなど、夢が広がりまくりです。dockerに感謝

# これからの課題
いままでworksで配信していたクソアプリがAPIと静的コンテンツの2分化の必要がありそうな気がしてきているのでそれのコード改修などがあります。なので、Rubyコンテナをworksの`docker-compose.yml`に追加してとかしなければなりません…

# 最後に
**誕生日プレゼント、待ってます。**
https://www.amazon.jp/hz/wishlist/ls/3FGFSE5EZKEM3?ref_=wl_share