こんにちは。SI部の r_maeda です。
先日、M1 チップの MacBook Pro を新しく会社から割り当ててもらいました。
これまで Rails アプリの開発には VirtualBox + Vagrant で作成した Linux VM を使うことが多かったのですが、現状 M1 Mac では VirtualBox を使うことができません。
これを機に、と Docker Compose を使って開発環境の仮想化を進めています。
そんな中で、具体的な設定の例や解説があまり出てこなかった、Docker Network に関する記事を書きました。
M1 Mac 以外でも利用できるものとなっていますので、ぜひ最後まで読んでいってください。
今参画しているプロジェクトでは、1つの Web アプリケーションを構成する複数のサブシステムを取り扱っており、それぞれのサブシステムを個別の Git リポジトリで管理しています。
workdir/
1st-system/
.git/
app/
config/
db/
spec/
Dockerfile.dev
docker-compose.yaml
...
2nd-system/
.git/
...
3rd-system/
.git/
...
...
今回 Docker Compose で開発用の仮想環境を構築するにあたり、Dockerfile および docker-compose.yaml ファイルはリポジトリ単位で作成することにしました。
普段は「サブシステム単位でアプリコードを修正し、自動テストを実行する」事が多いため、「RDB/Redis など、自動テストを実行するために必要なサービス」が起動していれば十分であり、「全てのサブシステムの Docker コンテナを一度に立ち上げるような Compose 環境」を作っても、その起動待ちの時間のほうがネックとなるためです。
しかしながら、各サブシステムは別のサブシステムと HTTP 通信を行うことがあるため、時には複数サブシステムのコンテナを同時に起動して、実際に組み合わせて動作を確認したいこともあります。
そのための環境をローカルの開発環境でも用意できたら嬉しいな、というのが今回やりたいことです。
今回 Docker Compose を使って仮想化したいサブシステムは以下の2つです。
これらの2つのサブシステムは、以下の通り組み合わせて利用します。
CMS は SSO に「コンテンツ管理者はログインしているか?」を問い合わせる必要があり、SSO は CMS に「コンテンツ管理者がログアウトした」ことを通知する必要があります。
つまり、SSO と CMS は相互にネットワーク通信を行う必要があります。
まずは何も考えずに、 SSO アプリを単体で起動するための docker-compose.yaml を書いてみましょう。
version: '3.8'
services:
db:
image: postgres:14
environment:
POSTGRES_USER: username
POSTGRES_PASSWORD: password
volumes:
- pgdata:/var/lib/postgresql/data
sso:
build:
context: .
dockerfile: Dockerfile.dev
command: ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
depends_on:
- db
environment:
DATABASE_URL: postgres://username:password@db
ports:
- ${SSO_PORT:-3000}:3000
volumes:
pgdata: {}
docker compose 環境のコンテナを起動します。
% docker compose up -d
⠿ Network sso_default Created 0.0s
⠿ Container sso-db-1 Created 0.0s
⠿ Container sso-sso-1 Created
さて、ログを見てもらうと分かるように、sso_default
という名前の Docker Network が作成されています。
(作成される Network, Container の prefix は、docker-compose.yaml が存在するディレクトリ名などによって変化します。)
そして、コンテナ起動後に Docker Network のメタデータを見ると、sso-db-1
と sso-sso-1
の 2つのコンテナが sso_default
ネットワークに接続されていることが確認できます。
% docker network inspect sso_default | jq '.[0].Containers'
{
"106b4f5d41d939ea680f1e76a7512c784135cc0828c6ef40bf6f031e7b853284": {
"Name": "sso-db-1",
"EndpointID": "05cde234ffe13dc8621b7bb0ebe7d69eadfe57e18e1c3fde62f91ce2f26ad226",
"MacAddress": "02:42:ac:19:00:02",
"IPv4Address": "172.25.0.2/16",
"IPv6Address": ""
},
"40f693f11f8a28d2c71c1040bd9752f53001c4049e21f6f9e4f16e660fe10df6": {
"Name": "sso-sso-1",
"EndpointID": "13ff389930dabdbf9724c403c03480258e86316108234350ca85d151fb781591",
"MacAddress": "02:42:ac:19:00:03",
"IPv4Address": "172.25.0.3/16",
"IPv6Address": ""
}
}
SSOコンテナと DBコンテナは同一の sso_default ネットワークに接続されているため、SSOコンテナから DBコンテナで起動している PostgreSQL にアクセスすることができます。
コンテナにアクセスするために必要な FQDN は、コンテナのメタデータから確認することができます。
% docker container inspect sso-db-1 | jq '.[0].NetworkSettings.Networks.sso_default.Aliases'
[
"sso-db-1",
"db",
"106b4f5d41d9"
]
デフォルトでは「コンテナID」「コンテナ名」「サービス名」が利用できますが、docker-compose.yaml 上で hostname
や aliases
を設定することで、これを変更することもできます。
さて、SSOコンテナから DBコンテナへのアクセスは、デフォルトで作成される Docker Network を利用していることが分かりました。
しかし、別の docker-compose.yaml で管理されている CMSコンテナから SSOコンテナへのアクセスを許可するためには、デフォルトで作成される Docker Network だけでは設定が十分ではありません。
そこで、明示的に Docker Network を作成しましょう。SSO 用の docker-compose.yaml を以下の通り変更します。
version: '3.8'
services:
db:
image: postgres:14
environment:
POSTGRES_USER: username
POSTGRES_PASSWORD: password
networks:
- internal
volumes:
- pgdata:/var/lib/postgresql/data
sso:
build:
context: .
dockerfile: Dockerfile.dev
command: ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
depends_on:
- db
environment:
DATABASE_URL: postgres://username:password@db
networks:
- internal
- external
ports:
- ${SSO_PORT:-3000}:3000
networks:
internal:
driver: bridge
internal: true
external:
driver: bridge
internal: false
volumes:
pgdata: {}
追加箇所は以下の通りです。Docker Network を 2つ作成し、それぞれにコンテナを接続しています。
前述の sso_default
ネットワークに代わり、SSOコンテナから DB コンテナにアクセスするために使うネットワークです。internal: true
を設定することにより、外部との通信を制限しています。
(開発用途なのでそんなに神経質になる必要もありませんが、DB を無駄に公開ネットワークに置きたくないので設定しました)
他の Docker コンテナを接続することで、SSO へのアクセスを許可するための公開ネットワークです。
現状このネットワークには SSO コンテナが接続しているのみですが、一旦はこれで大丈夫です。
作成されるネットワークの名前は {docker-compose.yaml があるディレクトリ名}_${docker-compose.yaml に書いた key}
(今回の場合 sso_internal
/ sso_external
)となりますが、name
を指定することで任意の名前で作成することも可能です。
networks:
external:
driver: bridge
internal: false
name: custom_name_for_sso_external_network
なお、作成されたネットワークの名前は、docker compose up
を実行した際のログや、docker network ls
の実行結果等から確認できます。
続いて、CMS コンテナを sso_external
ネットワークに接続しましょう。
CMS 用の docker-compose.yaml を以下の通り作成します。
version: '3.8'
services:
db:
image: postgres:14
environment:
POSTGRES_USER: username
POSTGRES_PASSWORD: password
networks:
- internal
volumes:
- pgdata:/var/lib/postgresql/data
cms:
build:
context: .
dockerfile: Dockerfile.dev
command: ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
depends_on:
- db
environment:
DATABASE_URL: postgres://username:password@db
networks:
- internal
- external
ports:
- ${CMS_PORT:-3001}:80
networks:
internal:
driver: bridge
internal: true
external:
name: sso_external
external: true
volumes:
pgdata: {}
SSO と同じように
を設定しています。ここで注目してほしいのは、networks.external
の設定です。
networks:
external:
name: sso_external
external: true
SSO では driver
と internal: false
を指定しましたが、CMS では name
と external: true
を指定しています。
違いは以下の通りです。
internal: false
external: true
これで、sso_external
ネットワークに SSO コンテナと CMS コンテナを接続することができました。
流石に業務アプリをここに書くわけにはいかないため、代わりに NGINX を使って簡単な Web アプリを作ります。
コンテナ間の通信の確認には、 curl を使います。
/
にアクセスすると Hello SSO!
の文字列を返すだけのアプリケーションです。
sso/
docker-compose.yaml
public/
index.html
version: '3.8'
services:
sso:
image: nginx:stable-alpine
networks:
- external
ports:
- ${SSO_PORT:-3000}:80
volumes:
- ./public:/usr/share/nginx/html:ro
networks:
external:
driver: bridge
internal: false
Hello SSO!
こちらは /
にアクセスすると Hello CMS!
の文字列を返します。
cms/
docker-compose.yaml
public/
index.html
version: '3.8'
services:
cms:
image: nginx:stable-alpine
networks:
- external
ports:
- ${CMS_PORT:-3001}:80
volumes:
- ./public:/usr/share/nginx/html:ro
networks:
external:
name: sso_external
external: true
Hello CMS!
ひとまずダミーの SSO を起動してみましょう。
問題なければ、ホストの Mac から http://localhost:3000/
にアクセスすることで Hello SSO! の文字列が表示されるはずです。
$ cd path/to/sso
$ docker compose up -d
$ curl http://localhost:3000/
Hello SSO!
次に、ダミーの CMS を起動します。
こちらは問題なければ、 http://localhost:3001/
から Hello CMS! の文字列が返ってくるはずです。
$ cd path/to/cms
$ docker compose up -d
$ curl http://localhost:3001/
Hello CMS!
続いて Docker コンテナ間の通信ができることを見ていきましょう。
まずは CMS から SSO にアクセスできることを確認します。
$ cd path/to/sso
$ docker compose up -d sso # SSOコンテナの起動
$ cd path/to/cms
$ docker compose run --rm cms curl http://sso:80/ # CMSコンテナから SSOコンテナへのアクセス
Hello SSO!
今度は逆に、SSO から CMS にアクセスしてみます。
$ cd path/to/cms
$ docker compose up -d cms # CMSコンテナの起動
$ cd path/to/sso
$ docker compose run --rm sso curl http://cms:80/ # SSOコンテナから CMSコンテナへのアクセス
Hello CMS!
おめでとうございます!
別々の Docker Compose 環境に定義した、SSO と CMS の 2つのコンテナ間で相互に HTTP 通信ができました!