DockerSwarmでデプロイしたコンテナを自動的にtraefikに登録する

# 概要

swarmでデプロイしたら特に何もしなくてもtraefikにルーティングルールが追加されてサービスが公開できるようにする。

もし、さくらのクラウドで試すなら1Hでやり切れば30円くらいで試せる。2Hだと60円。

# どうしてこの記事を書いたか

以前書いた、「[docker swarmを一から構築してrolling updateとかしつつ、HAproxyを介してサービスを公開する](https://qiita.com/tomokitamaki/items/9424057b07b3ea6b3cc9)」という方法だと、
新しいコンテナをデプロイする度にHAProxyのconfig書き換えないといけないので。
それはやばいから。

[Docker + consul-template で HAProxy のバックエンド登録の自動化を試す](https://cloudpack.media/9035) という
ためになる記事にもあるようにHAProxyでも解決する方はありそうだけど、traefik使ってみたいじゃないか。

# 準備(前提)

- DowckerSwarmでマネージャーとノードを一台づつ構築済み  
例えば、 `docker node ls` すると以下の感じになっている。

```shell
ubuntu@swarm-manager001:~$ sudo docker node ls
[sudo] ubuntu のパスワード: 
ID                            HOSTNAME           STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
c0oca505hi63sho914quutct7 *   swarm-manager001   Ready     Active         Leader           20.10.11
itcdqn487rk97gx044igqqfab     swarm-worker001    Ready     Active                          20.10.11
ubuntu@swarm-manager001:~$ 
```

- test_proxy という名前でオーバーレイネットワークを構築済み　　
e.g. `sudo docker network create --driver overlay test_proxy` っていう感じで作れる。

- https://github.com/tomokitamaki/train_traefik をclone しておく　　
`git clone https://github.com/tomokitamaki/train_traefik.git` 的な感じ。

- traefikのバージョンは `2.5.5` です


# traefikをデプロイする

cloneしたディレクトリ内で以下のコマンドを実行する。

```shell
sudo docker stack deploy -c traefik.yaml traefikz

e.g.
ubuntu@swarm-manager001:~/train_traefik$ sudo docker stack deploy -c traefik.yaml traefikz
Creating service traefikz_traefiks
ubuntu@swarm-manager001:~/train_traefik$ 
```

## traefik の管理画面を見てみる

swarmのクラスタ内のマネージャやノードのIPの8080番ポートにブラウザでアクセスしてみる。

```url
e.g. 例えばこんなURL
http://192.168.1.20:8080
```

アクセスしたら以下みたいななんかかっこいい画面が見えたらOKOK
![スクリーンショット 2021-12-19 17.51.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86853/e9edb8fe-579f-6562-090a-2672e0028e48.png)

## traefik.yaml の解説

```
version: "3.4"

services:
  traefiks: # サービスの名前
    image: traefik:latest
    volumes:
       - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      - "--providers.docker.swarmMode=true" # swarmmodeで動かすという宣言
      - "--api.insecure=true" #ダッシュボードを有効にするという宣言
      - "--providers.docker.exposedByDefault=false" #trueだとデプロイされたコンテナを全部もれなくtraefikが見つけちゃうので、見つけてほしいものだけにする宣言
      - "--providers.docker.network=test_proxy"# test_proxy というオーバーレイネットワーク上のコンテナを見つけるという宣言
    ports:
      - target: 80
        published: 80
      - target: 8080
        published: 8080
    deploy:
      placement:
        constraints:
          - node.role == manager
    networks:
      - test_proxy

networks:
  test_proxy:
    external: true　# test_proxy というネットワークはyamlの管理外という宣言
```

# nginx をデプロイする

cloneしたディレクトリ内で以下のコマンドを実行する。

```shell
sudo docker stack deploy -c nginx.yaml nginxz

e.g.
ubuntu@swarm-manager001:~/train_traefik$ sudo docker stack deploy -c nginx.yaml nginxz
Creating service nginxz_nginxs
ubuntu@swarm-manager001:~/train_traefik$ 
```

## nginxのあの画面を見る

swarmのクラスタ内のマネージャやノードのIPの80番ポートにブラウザでアクセスしてみる。

```url
e.g. 例えばこんなURL
http://192.168.1.20
```

いつもの "welcome" が見えたらOKOK.

次は、ヘッダーのHostに "nginx.tmktmk.domdom.burger" を指定してみる。
curlでやってみよう。
例えば、　`curl -H Host:nginx.tmktmk.domdom.burger 192.168.1.20` な感じでやってみて、
welcomeなHTMLが返ってきたらいい感じ！

```shell
e.g. 例えば以下の感じ。

ubuntu@swarm-manager001:~/train_traefik$ curl -H Host:nginx.tmktmk.domdom.burger 192.168.1.20
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
ubuntu@swarm-manager001:~/train_traefik$ 
```

## nginx.yaml の解説

```yaml
version: "3.4"

services:
  nginxs:
    image: nginx:latest
    ports:
      - target: 80
        published: 9000
    deploy:
      placement:
        constraints:
          - node.role == worker #　コンテナはworkerの役割のノードで動かすという宣言
      replicas: 2
      labels:
        - "traefik.enable=true" # traefikに見つけてもらいたいという宣言
        # - "traefik.http.routers.nginxs.rule=Host(`me.example.com`)"
        - "traefik.http.routers.nginxs.rule=PathPrefix(`/`) || HOST(`nginx.tmktmk.domdom.burger`)"　#　ルーティングのルールとして "/" か　ヘッダーのHostが "nginx.tmktmk.domdom.burger" であることを宣言 
        - "traefik.http.services.nginxs.loadbalancer.server.port=80"　# ルールに当てはまるものはコンテナの80番ポートにルーティングするという宣言
    networks:
      - test_proxy


networks:
  test_proxy:
    external: true
```

# fastapi をデプロイする

cloneしたディレクトリ内で以下のコマンドを実行する。

```shell
sudo docker stack deploy -c fastapi.yaml fastapiz

e.g.
ubuntu@swarm-manager001:~/train_traefik$ sudo docker stack deploy -c ./fastapi.yaml fastapiz
Creating service fastapiz_fastapis
ubuntu@swarm-manager001:~/train_traefik$ 
```

## 訳あって nginxz_nginxs のサービスを削除する

以下のコマンドで削除する。
`sudo docker service rm nginxz_nginxs`

```shell
サービス名は以下のコマンドで見れる。NAMEの列がサービス名。

ubuntu@swarm-manager001:~/train_traefik$ sudo docker service ls
ID             NAME                MODE         REPLICAS   IMAGE                                      PORTS
qckdsxxn8ck7   fastapiz_fastapis   replicated   1/1        tiangolo/uvicorn-gunicorn-fastapi:latest   *:8000->80/tcp
pfu9szua7wsf   nginxz_nginxs       replicated   2/2        nginx:latest                               *:9000->80/tcp
u85fs2isk3lz   traefikz_traefiks   replicated   1/1        traefik:latest                             *:80->80/tcp, *:8080->8080/tcp
ubuntu@swarm-manager001:~/train_traefik$
```

## fastapiにアクセスしてみる


ヘッダーのHostに "fastapi.tmktmk.domdom.burger" を指定してみる。
curlでやってみよう。
例えば、　`curl -H Host:fastapi.tmktmk.domdom.burger 192.168.1.20` な感じでやってみて、
それっぽいjsonが返って来たらいい感じ！

```shell
e.g.

ubuntu@swarm-manager001:~/train_traefik$ curl -H Host:fastapi.tmktmk.domdom.burger 192.168.1.20
{"message":"Hello world! From FastAPI running on Uvicorn with Gunicorn. Using Python 3.9"}ubuntu@swarm-manager001:~/train_traefik$ 
```

ちなみにパスを `/docs` とするとopenapiの内容が返ってくる。

```
e.g.

curl -H Host:fastapi.tmktmk.domdom.burger 192.168.1.20/docs

    <!DOCTYPE html>
    <html>
    <head>
    <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
    <link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
    <title>FastAPI - Swagger UI</title>
    </head>
(後略)
```

## fastapi。yamlの解説

省略。nginxのと一緒です。

# nginxz_nginxs　のサービスを削除しないとどうなるか

nginxのコンテナにルーティングされてしまい、fastapiのコンテナにルーティングされません。
なぜなら、nginxのコンテナのルーティングに "/" なものというルールが入っているのでそちらが先にマッチングしてしまいます。
どういった規則(ロンゲストマッチとかデプロイ順とか)でマッチングするのかは調べていません。さーせん。

# まとめ

ヘッダーのHostでルーティングするなら、managerやノードのIPに対して `*.tmktmk.domdom.burger` みたいなAレコードを作っておくと、シュシュっとコンテナを公開出来て便利。

traefikでHTTPSを終端するとか、let's encrypt の証明書の自動更新をするとか他にも色々出来るので、
やってみたいぞ！ってなったら公式ドキュメントをみよう。
https://doc.traefik.io/traefik/contributing/documentation/

あと、どこかのバージョンから引数とか変わったみたいなので、
ググって出てくる昔の記事のconfigでは動かなかったりするよ！