HashiCorp Nomad で作るコンテナ実行基盤

2019年3月から、さくらインターネットで働いている、自称「構成管理おじさん」です。

チームでの役割としては下記のようなことをやっています。

- コンテナ実行基盤の構築
- メトリクス管理、監視基盤の構築
- ログ管理、監視基盤の構築

今回は、コンテナ実行基盤の構築について少しだけお話しさせていただきます。

# 内容

- 世の流れは Kubernetes だけど、 Docker + Nomad + Consul + Traefik を組み合わせるとこんなことができ、この構成に至るまでにどのように思考していったのかについて書いています。
- Nomad + Consul を利用したコンテナオーケストレーションを行っている国内の企業がここにもいますよということをアピールしておきます。(いつか対面でお話できる日がくるとよいなと思っています。)

    - LINEの金融系サービスを支えるサーバーエンジニアの仕事  
    Consul・Nomad・Envoyを利用したコンテナオーケストレーションシステム開発
    - https://logmi.jp/tech/articles/322097

# 入社前のコンテナ経験

- LXC
    - 自宅や自身の業務 PC にてミドルウェアの検証などに利用していました。
- Docker
    - 2013年12月の出始めのときに少し触った程度です。

前職の仮想サーバーでアプリケーションを実行するという構成から、現職では、アプリケーションを Docker コンテナで実行するという構成になり、技術要素が、仮想サーバーからコンテナになったなと感じています。

# モチベーション

何故コンテナ技術に触れるに至ったのかというところをお話ししていきたいと思います。

入社時に上長からは「 Prometheus を使った監視基盤を作ってもらいたい」と言われました。

これからやっていきたいところとして手を付けられていない部分である監視基盤を主導していってもらいたいということを言われました。
口頭での情報だけでは少ないなと思ったので、現状チームにて取り組んでいることをキャッチアップするため、社内にあるドキュメントやコードを読み漁ることにしました。
すると下記のような思いを読み取れました。

- アプリケーションは Docker コンテナとして動かしていきたい
- スケールアウトが容易なシステム基盤にしていきたい
- メトリクス管理、監視の基盤が欲しい
- ログ管理、監視の基盤が欲しい

やっていきたいという思いは確認できましたが、この時点で動いているものは一つもありませんでした。
これらを動くものとして形にしていくことが自身の役割であると考え、まずはコンテナを実行する基盤を構築することにしました。

# Docker

![horizontal-logo-monochromatic-white.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37462/c7feb9dc-75c9-9320-45f0-849b889ac998.png)

Docker
https://www.docker.com/

コンテナとして Docker を採用することにしました。
「アプリケーションを Docker コンテナとして動かしていきたい」思いを読み取ったという部分もありますが、 Docker を採用したのはそれだけではなく、下記のようなことも考えました。

## デファクトスタンダード

Docker がコンテナのデファクトスタンダートとなっており周辺のツールが充実していることも採用の理由の一つです。

## アプリケーションの依存関係を切り離せる

前職ではバッチサーバーなるものの管理を任され、ホストサーバーにはバッチを動かすためのランタイムと現地ビルドのためのビルド用のパッケージがモリモリと追加され肥大化した結果、いま動いているバッチが動く環境ではあるが、それぞれのバッチを実行するための依存関係がわからないという辛みを味わいました。
それぞれのアプリケーションの依存関係は、それぞれの Docker コンテナに閉じ込めることで、ホストサーバーからアプリケーションの依存関係を切り離せるというのはメリットであると考えました。

## ポータビリティを確保できる

先ほどのバッチサーバーの話も含め、アプリケーションを実行するための依存関係がわからなくなり移行が捗らないとう現場をたくさん目の当たりにしてきました。
ホストサーバーからアプリケーションの依存関係を切り離せるとホストサーバーに必要になってくるのは、 Docker コンテナを動かす実行環境だけになるため、アプリケーション自体のポータビリティを確保できるという点もメリットであると考えました。

## 使っていくにあたって

アプリケーションは Docker コンテナにするとして、どうデプロイするのかという問題に当たりました。

Ansible にて Docker コンテナをデプロイすることはできましたが、 Ansible をインフラ担当がサーバーのセットアップに、アプリケーション開発者が Docker コンテナのデプロイにと Ansible に 2 つの役割りを持たせたくはありませんでした。
両者が同じツールを利用するのではなく、アプリケーション開発者からは Ansible の存在を隠蔽し、別の手段をもって Docker コンテナをデプロイする世界を作りたいなと考えました。
Ansible は Docker の実行環境をセットアップをするところまでと決め、 Docker コンテナをデプロイするための手段を探すことにしました。

# Nomad

![nomad.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37462/b04d13eb-b61c-2fb8-2540-073c8b5c640a.png)

Nomad
https://www.nomadproject.io/

入社前に時間があったので Kubernetes を触ってみましたが、セットアップして Nginx をデプロイしたところで、運用していくイメージが持てず力尽きました。現在のチームにて一緒にやっている方も同じようなところで力尽きていました。
やりたかったことは、コンテナ実行基盤の運用に疲弊することではなく、 Docker コンテナをデプロイしたいということでした。
ではどうするのかというところで、下記の記事が出て来たので Nomad を試してみることにしました。

- 多分あなたにKubernetesは必要ない
	- https://yakst.com/ja/posts/5455

Nomad は環境を構築して、 Docker コンテナをデプロイするまでに1日掛からず、なんだかやっていけそうな気がしました。

Nomad は Nomad Server, Nomad Client の構成で動作し、 Nomad Job と呼ばれるものを記述し実行することで、 Nomad Client に Docker コンテナが起動します。

Architecture
https://www.nomadproject.io/docs/internals/architecture.html

Job Specification
https://www.nomadproject.io/docs/job-specification/index.html

# Consul

![consul.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37462/0eb1e196-8214-e4d1-6730-1c97cc6d9205.png)

Consul
https://www.consul.io/

Nomad により Docker コンテナのアプリケーション(以下、コンテナアプリケーション)のデプロイを Ansible でやらない環境を構築することができました。

しかし Nomad Client 間を動きまわるコンテナアプリケーションに対してどのように名前解決し、接続するのかという問題に当たりました。

ホストサーバーとアプリケーションが一対一になっている従来の構成であれば、ホストサーバーの IP アドレスに対して DNS にて名前を付けておけば、その名前を使いアプリケーションに接続できていました。

Nomad では、コンテナアプリケーションをどの Nomad Client に配置するかは人間が決めるのではなく、 Nomad Server が決めます。

Consul と Nomad を組み合わせることで、 Nomad Client にデプロイされたコンテナアプリケーションの名前解決が可能になるということがわかりました。

Nomad Reference Architecture
https://www.nomadproject.io/guides/install/production/reference-architecture.html

Consul Architecture
https://www.consul.io/docs/internals/architecture.html

ここで Service Discovery という概念を知ることになりました。
従来のサーバー構成から、サービスという概念のレイヤーにてコンテナアプリケーションなどに接続するための名前解決の仕組みが提供されるようになったのだなと感じました。

どの Nomad Client にコンテナアプリケーションが配置されたかという情報は Consul に登録され、 Consul の提供している DNS Interface や HTTP API にて名前解決できるようになります。

DNS Interface
https://www.consul.io/docs/agent/dns.html

Service - Agent HTTP API
https://www.consul.io/api/agent/service.html

Catalog HTTP API
https://www.consul.io/api/catalog.html

# Traefik

![traefik.logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37462/6c3116d4-1fc4-0307-125c-8e3991713cda.png)

Traefik
https://traefik.io/

Nomad Client にデプロイされたコンテナアプリケーションは、 Consul により名前解決できるようになりました。

しかし、これは Nomad + Consul の動作しているサーバーだけの話で、これらが動作していないサーバーは、 Nomad Client のコンテナアプリケーションと通信できません。
橋渡しを担うエンドポイントが必要になります。

Consul は、どの Nomad Client に何のコンテナアプリケーションが配置されているかというカタログ情報を持っており、 Traefik は Consul のカタログ情報をもとに HTTP のエンドポイントを生成できるようになっています。

Consul Catalog Provider
https://docs.traefik.io/v1.7/configuration/backends/consulcatalog/

Nomad Job に下記を設定すると Consul に登録され Traefik は HTTP エンドポイントを生成します。

Load Balancing with Traefik
https://learn.hashicorp.com/nomad/load-balancing/traefik

```
        tags = [
          "traefik.tags=service",
          "traefik.frontend.rule=PathPrefixStrip:/myapp",
        ]
```

開発者がアプリケーションをデプロイし、 インフラ担当がロードバランサにエンドポイントを登録するという、よくあるオペレーションが、ここでは登場しないのだということを思いました。

# 構成図

![nomad.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37462/dcdcda73-892f-2b76-9efc-b1fb8d48d621.png)

テキストばかりだったので、最終的にどのようなサーバー構成になっているのかということを図にしました。 Nomad Client に WebAPI のコンテナアプリケーションが起動し、エンドポイントから参照できるようになるまでを図に起こしています。

Nomad Job には下記を設定していると仮定します。

```
        tags = [
          "traefik.tags=service",
          "traefik.frontend.rule=PathPrefixStrip:/webapi",
        ]
```

1. WebAPI_Deploy
	1. Nomad Server に対して WebAPI のデプロイを指示
	2. Nomad Server は Nomad Client に対して WebAPI の起動を指示
	3. Nomad Client は WebAPI の Docker コンテナを起動
	4. Nomad Server は Consul Server に WebAPI の情報を登録
2. HTTP_Request
	1. Traefik は Consul のカタログ情報から /webapi のエンドポイントを生成
	2. /webapi にリクエストが来ると Nomad Client の WebAPI にルーティングされる

この構成において、人間がやることは 1-1 の Nomad Job を実行するというところだけになります。
1-2 以降は、各ソフトウェアがよしなに連携していきます。

# まとめ

- 影も形も無く、ほぼ全てがはじめての取り組みという状態から、動くものが形になりました。
- 構築を進める中で当たった問題というのは、解決する技術が揃っているということがわかりました。
- アプリケーションを Docker コンテナとして動かせる環境が構築できました。
- アプリケーション開発者から Ansible を隠蔽することができました。
- Nomad Job を学習するコストはありますが、開発者に対してアプリケーションのデプロイを統一されたインターフェースにて提供できるようになりました。
- コンテナ実行基盤のセットアップは Ansible 、アプリケーションのデプロイは Nomad とインフラとアプリケーションの構成管理の範囲を明確に分けることができました。
- Nomad Client を追加していけばスケールアウトが可能です。

如何でしたでしょうか。
Kubernetes でなくても、ここまで出来るということを感じていただけたらと思います。
最後まで読んでいただき、ありがとうございました。