この記事は、Japan AWS Jr. Champions Advent Calendar 2024の14日目の記事です。
はじめに
株式会社Finatextでサーバーサイド・AWSエンジニアをしている @takuma5884rbbです。
私が普段触れているシステムは、AWS上に構築されたマイクロサービスアーキテクチャで運用されています。機能ごとにモジュールを分けて開発・デプロイを行うマイクロサービスアーキテクチャは、開発サイクルを高速化し、メンテナンスやスケーリングを簡素化します。一方で、モジュール間のインターフェースや接続方法について考慮する必要があり、開発手法にある種の制約を与えることもあります。本記事では、そういったケースへの対処法の一つをご紹介します。
AWSを用いたソフトウェア開発とその課題
AWS上にデプロイされた複数のコンテナ(サーバー)を接続する際に考慮する点として、ネットワークアクセスの制限は外せないでしょう。
この課題に対するソリューションの一つが、Amazon Route53におけるPrivate Hosted Zoneです。
Private Hosted Zoneは設定したVPCのみからアクセスできるHosted Zoneです。
つまり、
- アーキテクチャ内に存在するモジュールを特定のVPCに配置する
- そのVPCを関連づけたHosted Zoneを作成
- そのHosted Zoneを用いてDNSレコードを作成
- それらのDNSレコードを用いて、各種モジュールへアクセスできるようにルーティングを設定
という手順を踏むことで、システム内部の安全な通信を確立させることができます。
一方で、アプリケーション開発のサイクルにおいて全ての作業をAWS上で行うわけではありません。例えば開発用EC2インスタンスを建ててその中で開発するとなると追加のコストがかかりますし、ローカルで開発したコードをAWS環境にデプロイして動作確認を行うにしても、CDパイプラインを用意したとしても十数分のリードタイムが生じてしまうでしょう。
したがって、
ローカル環境にてコーディングから動作確認まで完結する
状態を作ることができれば、ソフトウェアの開発サイクルを高速に回すことができます。
次のセクションから、そのための方法の一つをご紹介します。
本編
AWS Systems Manager Session Managerを用いた接続方法
まずは、ローカルからPrivate Hosted Zoneへの接続を実現します。
Private Hosted Zoneが特定のVPCからしかアクセスできない以上、そのVPC上に起動しているインスタンスに接続することを考えます。ここでは、AWS Systems Manager Session Managerを用います。
AWS CLIを用いる場合はこんな感じ
aws ssm start-session --target {インスタンスID} --document-name \
AWS-StartPortForwardingSessionToRemoteHost \
--parameters {endpoint},portNumber={リモート側のポート番号},localPortNumber={ローカルで使用したいポート番号} --region {region}
Goでスクリプトを書く場合はこんな感じ
func (c *ssmClient) StartSession(ctx context.Context, target, host, port string) (string, error) {
input := &ssm.StartSessionInput{
Target: aws.String(target),
Parameters: map[string][]string{
"host": {host},
"portNumber": {"3306"},
"localPortNumber": {port},
},
DocumentName: aws.String("AWS-StartPortForwardingSessionToRemoteHost"),
}
output, err := c.svc.StartSession(ctx, input)
if err != nil {
return "", err
}
return *output.SessionId, nil
}
余談ですが、start-session
の際のtargetにはFargateタスクを指定することができます。このときの指定方法は以下となります。
ecs:{ECSクラスタ名}_{ECSタスクID}_{ランタイムID}
ランタイムIDはDescribeTasksAPIから取得することができます。
{
...
"tasks": [
{
...
"availabilityZone": "string",
"capacityProviderName": "string",
"clusterArn": "string",
"connectivity": "string",
"connectivityAt": number,
"containerInstanceArn": "string",
"containers": [
{
...
"runtimeId": "string", // これ
"taskArn": "string"
}
],
"cpu": "string",
"createdAt": number,
"desiredStatus": "string",
...
}
]
}
こうすることで、ローカルのポートに対するネットワークアクセスがEC2インスタンスの特定ポートにフォワードされるようになります。
Dockerからの接続
ローカルでの開発環境構築にDockerを用いている方は多いと思います。ローカルから先ほどのポートフォワードを使用するためには一度localhostにルーティングする必要がありますが、先ほどの図のように、各種マイクロサービスへのアクセスをALBでルーティングすることを考えると、ホストヘッダーはAWS環境の設定に合わせておく必要があります。
例)ALBのListener Ruleで「ホストヘッダーがhoge.private.net
の場合にマイクロサービスAにルーティングする」という設定がある場合、ローカルからのアクセスでhostをlocalhost
にしてしまうと、ポートフォワードでEC2インスタンスにアクセスするまではいいものの、Listener Ruleに適合できずマイクロサービスにアクセスできなくなってしまいます。
ここでは、Dockerコンテナ内の/etc/hosts
を書き換えることを考えます。
Dockerコンテナではhost.docker.internal
というDNSを用いてホストへの名前解決を行う仕組みがあり、
host.docker.internal
は開発用途のみの利用が推奨されています
https://docs.docker.jp/v19.03/docker-for-mac/networking.html#mac-i-want-to-connect-from-a-container-to-a-service-on-the-host
コンテナ内部からnslookup
を実行することで、解決先のIPアドレスを確認することができます(このIPアドレスはローカル環境やDockerエンジンなどによって異なるようです)。
$ docker container exec -it container-name sh
/myContainer # nslookup host.docker.internal
Server: 127.0.0.xx
Address: 127.0.0.xx:yy
Non-authoritative answer:
Non-authoritative answer:
Name: host.docker.internal
Address: 192.168.xxx.yyy
/etc/hosts
を編集し、各種マイクロサービスに向けて利用したいhostをこのIPに解決させてやることで、ホストヘッダーを保ったままローカルへのフォワードを行うことができます。
/myContainer # cat /etc/hosts
127.0.0.1 localhost
(略)
192.168.xxx.yyy hoge.private.net
docker-composeを利用して/etc/hosts
を編集する方法は以下です。
services:
service:
build:
...
extra_hosts:
- "hoge.private.net:192.168.xxx.yyy"
ポートフォワードを利用する都合上、エンドポイントにはポート番号を付与する必要はあります。
- SERVICE_A_ENDPOINT=http://hoge.private.net
+ SERVICE_A_ENDPOINT=http://hoge.private.net:{ポートフォワードで使用するローカルのポート番号}
ここまでの手順を踏むことで、ローカルのDockerコンテナ→EC2インスタンス→Private Hosted Zone→ALB→マイクロサービスという接続を確立することができます!
まとめ
マイクロサービス間の通信にはPrivate Hosted Zoneを利用して安全な接続を確立させることができますが、ローカルから単純にアクセスすることができない問題がありました。
そしてこれを解決する手段として、AWS Systems Manager Session Managerを利用したポートフォワードとホスト名解決、Dockerを用いる場合の注意点に触れ、ローカルの開発環境からの接続方法について説明してきました。
これでローカルでの開発サイクルを爆速で回すことができますね!
今後皆さんに、快適なAWSライフが訪れることを祈って。
参考文献
- マイクロサービスの概要 | AWS
- AWS Systems Manager Session Managerでポートフォワーディングを使用してリモートホストに接続する | Amazon Web Services ブログ
- DescribeTasks - Amazon Elastic Container Service
- AWS Systems Manager Session Manager - AWS Systems Manager
- start-session — AWS CLI 2.22.16 Command Reference
- Compose ファイル・リファレンス — Docker-docs-ja 1.10.0 ドキュメント