こんにちは、開発部の澤井です。

前回は1つのサーバに複数のコンテナを配置して、ブリッジネットワークにおけるコンテナ間通信についてまとめました。

ブリッジネットワークは、同一ブリッジネットワークに配置したコンテナ同士でのみ通信できます。

一般に複数のサーバに配置したコンテナ間で通信する環境をマルチホストと呼びます。 また複数コンテナを管理するツールをコンテナオーケストレーションツールと呼びます。

代表的なコンテナオーケストレーションツールに以下があります。

  • Amazon ECS
  • Kubernetes
  • Docker Swarm

今回は簡単なマルチホストのサンプルをDocker Swarmで動かしたいと思います。

Docker Swarmでマルチホストを実現する技術

Docker Swarm

Docker Swarmは、Docker社が提供するオーケストレーションツールで複数のホストを集約して管理します。
Docker Swarmは、managerノードとworkerノードで構成されます。

overlayネットワーク

overlayネットワークは複数のDockerホスト間に分散ネットワークを作成します。
具体的にはVXLAN技術を使用してOSI参照モデルのレイヤ3に論理的なレイヤ2を構築して同一セグメントのように扱います。

ゴール

Docker SwarmでAWSに以下のアプリケーションを構築します。

  • 可用性を高めるために異なるサブネットにwebコンテナを配置する
  • dbコンテナはwebコンテナとは独立したサブネットに配置する

準備

本記事で使用するAmazon EC2はUbuntu 22.04.2 LTSを使用します。

Docker Engineをインストール

UbuntuにDocker Engineをインストールする手順はInstall Docker Engine on Ubuntuに記載されています。
本記事は各EC2インスタンスにDocker Engineがインストール済みであることを前提としています。

ホスト用EC2インスタンス&ネットワークを構築

VPC10.0.0.0/16に4つのサブネットを作成して、各1つのEC2インスタンスを配置します。

ノード サブネット プライベートIPアドレス ホスト名(hostname) パブリックIPアドレス
manager 10.0.0.0/24(パブリックサブネット) 10.0.0.10 ip-10-0-0-10 203.0.113.10
worker 10.0.1.0/24(プライベートサブネット) 10.0.1.10 ip-10-0-1-10 なし
worker 10.0.2.0/24(プライベートサブネット) 10.0.2.10 ip-10-0-2-10 なし
worker 10.0.3.0/24(プライベートサブネット) 10.0.3.10 ip-10-0-3-10 なし

プライベートサブネットはNATゲートウェイを使用して外部とアクセス可能にします。

EC2 セキュリティグループ

Swarmモードを使用するために以下ポートを開きます。

  • TCP port 2377 は、クラスタ管理通信のため
  • TCP と UDP の port 7946 は、ノード間の通信のため
  • UDP port 4789 はオーバレイ・ネットワーク・トラフィックのため

ref. swarm モード導入ガイド

workerのセキュリティグループ

Swarmモードセキュリティグループ

managerのセキュリティグループ

managerは上記に加えて2377を追加します。
また動作確認のために8000も追加します(こちらは本記事のサンプルアプリケーションの仕様でありDocker Swarmの仕様ではありません)。

Swarmモードセキュリティグループ

サンプルコード

簡単なサンプルなので本記事で使用するすべてのコードを掲載します。

web用PHP

<?php
// index.php
$redis = new Redis();
// ホストにサービス名を指定
$redis->connect('db', 6379);
$redis->incr('count');
?>
<html>
  <head>
    <title>Swarm sample</title>
  </head>
  <body>
    <p>
      <?= sprintf('表示回数:%d', $redis->get('count')); ?>
    </p>
  </body>
</html

web用Dockerfile

# Dockerfile
FROM php:latest

WORKDIR /usr/src/myapp

RUN pecl install -o -f redis \
&&  rm -rf /tmp/pear \
&&  docker-php-ext-enable redis

COPY . /usr/src/myapp

CMD ["php","-S", "0.0.0.0:8000"]

Docker Compose

# sample-stack.yml
version: '3'
services:
  web:
    image: web:latest
    ports:
      - "8000:8000"
    deploy:
      mode: replicated
      replicas: 2
      endpoint_mode: vip
      placement:
        constraints: [node.role!=manager, node.hostname!=ip-10-0-3-10]
    networks:
      - sample
  db:
    image: redis:alpine
    networks:
      - sample
    deploy:
      placement:
        constraints: [node.hostname==ip-10-0-3-10]
networks:
  sample:
    external: true

swarmを構築

managerを構築

manager10.0.0.10に構築します。

manager-ip-10-0-0-10$ sudo docker swarm init --advertise-addr=10.0.0.10

ノードを確認します。

manager-ip-10-0-0-10$ sudo docker node ls
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
xxxxxxxxxxx *   ip-10-0-0-10    Ready     Active         Leader           23.0.1

managerが作成されています。
後述するworkerを追加するコマンドは、以下コマンドで取得できます。

manager-ip-10-0-0-10$ sudo docker swarm join-token worker
To add a worker to this swarm, run the following command:

    docker swarm join --token {{トークン}} 10.0.0.10:2377

workerを構築

workerを構築します。

10.0.1.10のDocker Engineをswarmに参加させます。
10.0.1.10でコマンドを発行します。

worker-ip-10-0-1-10$ docker swarm join --token {{トークン}} 10.0.0.10:2377

managerでノードを確認します。
ノードが追加されています。

manager-ip-10-0-0-10$ sudo docker node ls
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
xxxxxxxxxxx *   ip-10-0-0-10    Ready     Active         Leader           23.0.1
xxxxxxxxxxx     ip-10-0-1-10    Ready     Active                          23.0.1

同様に10.0.2.10および10.0.3.10のDocker Engineもswarmに参加させます。

10.0.2.10のDocker Engineをswarmに参加させます。

worker2-ip-10-0-2-10$ docker swarm join --token {{トークン}} 10.0.0.10:2377

10.0.3.10のDocker Engineをswarmに参加させます。

worker3-ip-10-0-3-10$ docker swarm join --token {{トークン}} 10.0.0.10:2377

managerでノードを確認します。

manager-ip-10-0-0-10$ sudo docker node ls
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
xxxxxxxxxxx *   ip-10-0-0-10    Ready     Active         Leader           23.0.1
xxxxxxxxxxx     ip-10-1-0-10    Ready     Active                          23.0.1
xxxxxxxxxxx     ip-10-2-0-10    Ready     Active                          23.0.1
xxxxxxxxxxx     ip-10-3-0-10    Ready     Active                          23.0.1

overlay ネットワークを作成

manageroverlayネットワークを作成します。
172.20.0.0/24overlayネットワークをsampleという名前で作成します。

manager-ip-10-0-0-10$ sudo docker network create -d overlay --subnet 172.20.0.0/24 --attachable sample

web用イメージをビルド&各ノードに配布

manager-ip-10-0-0-10$ sudo docker build -f -t web:latest .
manager-ip-10-0-0-10$ sudo docker save web:latest | gzip > web-latest.tgz 

SCPなどで各ノードにweb-latest.tgzをコピーします。
10.0.1.1010.0.2.10にコピー済み仮定します。

コピー先でロードします。 例として10.0.1.10でロードします。

worker1-ip-10-0-1-10$ sudo docker load -i web-latest.tgz

以上で準備が整ったのでスタックをデプロイします。

デプロイ

デプロイの指示をDocker Composeで定義します。
sample-stack.ymlを再度記載します。

# sample-stack.yml
version: '3'
services:
  web:
    image: web:latest
    ports:
      - "8000:8000"
    deploy:
      mode: replicated
      replicas: 2  # ---(1)
      endpoint_mode: vip
      placement:
        constraints: [node.role!=manager, node.hostname!=ip-10-0-3-10]  # ---(2)
    networks:
      - sample
  db:
    image: redis:alpine
    networks:
      - sample
    deploy:
      placement:
        constraints: [node.hostname==ip-10-0-3-10]  # ---(3)
networks:
  sample:
    external: true

以下のようにデプロイします。

(1) webサービスを2つ稼働します (2) webサービスをmanagerノード、ip-10-0-3-10ノード以外に配置します (3) dbサービスをip-10-0-3-10ノードに配置します

managerでスタックのデプロイコマンドを発行します。

manager-ip-10-0-0-10$ sudo docker stack deploy --compose-file sample-stack.yml sample

デプロイを確認します。

manager-ip-10-0-0-10$ sudo docker stack ps sample
ID             NAME           IMAGE          NODE           DESIRED STATE   CURRENT STATE            ERROR     PORTS
xxxxxxxxxxx   sample_web.1   web:latest     ip-10-0-1-10    Running         Running 12 seconds ago
xxxxxxxxxxx   sample_web.2   web:latest     ip-10-0-2-10    Running         Running 12 seconds ago
xxxxxxxxxxx   sample_db.1    redis:alpine   ip-10-0-3-10    Running         Running 14 seconds ago

webサービスが10.0.1.1010.0.2.0、dbサービスが10.0.3.10にデプロイされました。

動作確認

http://203.0.113.10:8000 にアクセスします。
以下のようにアクセス数が表示されます。

表示回数:1

アクセスするたびにカウンターがアップするのでwebサービスとdbサービスが連携していることを確認できます。

メリット

Docker Swarmでデプロイしました。
とても簡単なサンプルですがマルチホストを使用することのメリットについてまとめます。

可用性

可用性を確認するために10.0.1.10のコンテナを強制的に停止してみます。

worker1-ip-10-0-1-10$ sudo docker ps --all
CONTAINER ID   IMAGE        COMMAND                  CREATED       STATUS       PORTS     NAMES
xxxxxxxxxxx   web:latest   "docker-php-entrypoi…"   4 hours ago   Up 4 hours             sample_web.1.xxxxxxxxxxx
worker1-ip-10-0-1-10$ sudo docker stop xxxxxxxxxxx

予想通りswarmが自動で新たなコンテナを起動しました。

worker1-ip-10-0-1-10$ sudo docker ps --all
CONTAINER ID   IMAGE        COMMAND                  CREATED          STATUS                        PORTS     NAMES
xxxxxxxxxxx   web:latest   "docker-php-entrypoi…"   15 seconds ago   Up 12 seconds                           sample_web.1.xxxxxxxxxxx
xxxxxxxxxxx   web:latest   "docker-php-entrypoi…"   4 hours ago      Exited (137) 18 seconds ago             sample_web.1.xxxxxxxxxxx

セキュリティ

外部に公開するサービスと公開の必要のないサービスを別サブネットに配置できました。
外部に公開する必要のないサービスをプライベートサブネットに配置することでセキュリティが高まりました。

まとめ

今回はマルチホストのコンテナ運用について理解を深めたくて、Docker Swarmを取り上げました。マルチホストの可用性などをコンテナレベルで確認することができてました。今後もオーケストレーションツールの理解を深めたいと思います。

参照