概要
AWSを使用しているとVPC内のリソースにアクセスするため踏み台サーバーを構築する機会があるかと思います。
EC2を踏み台サーバーとする選択肢もあると思いますが、今回はFargate
を使用して踏み台サーバーを構築し、ローカルからポートフォワードしてプライベートサブネット内に配置されたDBへ接続するまでを行います。
Fargate
のデプロイにはAWS Copilot
を使用します。
目的
- 踏み台サーバーに
Fargate
を使用してサーバーの管理コストを下げる -
AWS Copilot
を使用してコンテナのデプロイを簡略化する
前提
-
AWS Copilot
でVPCを作成済みcopilot app init example
copilot env init --app example --name dev --default-config
copilot env deploy --app example --name dev
- 作成したVPCのプライベートサブネットにRDBを作成済み
- RDBに接続可能なセキュリティグループを作成済み
セキュリティグループの作成
addonsでAWSリソースを作成する場合の例
copilot/envilonments/dev/addons/rds.yml
Parameters:
App:
Type: String
Description: Your application's name.
Env:
Type: String
Description: The environment name your service, job, or workflow is being deployed to.
Resources:
# ~~ RDSの作成部分省略 ~~
AllowDBAccessSecurityGroup: # DBに接続するため踏み台サーバーにアタッチするセキュリティグループ
Metadata:
'aws:copilot:description': 'A security group for your workload to access the db'
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: !Sub 'The Security Group for ServiceCluster to access ${App}-${Env}-rds.'
GroupName: !Sub ${App}-${Env}-allow-db-access-security-group
VpcId:
Fn::ImportValue:
!Sub '${App}-${Env}-VpcId'
DBClusterSecurityGroup:
Metadata:
'aws:copilot:description': 'A security group for your db'
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub The Security Group for the ${App}-${Env}-rds.
GroupName: !Sub ${App}-${Env}-DBSecurityGroup
SecurityGroupIngress:
- ToPort: 3306
FromPort: 3306
IpProtocol: tcp
Description: !Sub 'From the Aurora Security Group of the workload.'
SourceSecurityGroupId: !Ref AllowDBAccessSecurityGroup
VpcId:
Fn::ImportValue:
!Sub '${App}-${Env}-VpcId'
Outputs:
AllowDBAccessSecurityGroupId:
Description: Id of Service Cluster Security Group from environments addons.
Value: !Ref AllowDBAccessSecurityGroup
# DBと接続が必要なコンテナのCloudFormationでimportするためexportしておく
Export:
Name: !Sub ${App}-${Env}-AllowDBAccessSecurityGroup
踏み台サーバーの構築
ディレクトリ構成
copilot
├── bastion
│ └── Dockerfile
└── environments
└── dev
└── manifest.yml
copilot/bastion/Dockerfile
FROM gcr.io/distroless/static-debian11:debug
Dockerfileを作成したらcopilot svc init
コマンドでサービスを初期化します。
サービスのタイプはBackend Service
を選択します。
copilot svc init \
--name bastion \
--dockerfile './copilot/bastion/Dockerfile' \
--svc-type 'Backend Service'
すると、copilot/bastion/manifest.yml
というファイルができるので、中身を編集します。
# https://aws.github.io/copilot-cli/docs/manifest/backend-service/
name: bastion
type: Backend Service
image:
build:
dockerfile: ./copilot/bastion/Dockerfile
cpu: 256
memory: 512
platform: linux/x86_64
count: 1
exec: true
network:
vpc:
security_groups:
groups: # ここにRDSと接続可能なセキュリティグループを設定する
- 'sg-xxxxxx'
# CloudFormationのexportから取得する場合
# - from_cfn: ${COPILOT_APPLICATION_NAME}-${COPILOT_ENVIRONMENT_NAME}-AllowDBAccessSecurityGroup
# タスク定義の上書き
taskdef_overrides:
- path: ContainerDefinitions[0].PseudoTerminal
value: true
ポイントは以下です.
-
exec: true
- コンテナ内部に入れるように設定
-
network.vpc.security_groups.groups
- プライベートサブネット内に配置したRDSに接続できるセキュリティグループを追加
-
taskdef_overrides
の設定- 今回のDockerfileの書き方の場合、デプロイ後にすぐサービスが終了してしまうので永続化の設定が必要
ローカルで実行する場合のdocker run --tty
に相当する設定を追加することで起動したままにします
- 今回のDockerfileの書き方の場合、デプロイ後にすぐサービスが終了してしまうので永続化の設定が必要
デプロイ
copilot svc deploy --app example --env dev --name bastion
これでデプロイできたので次はコンテナに接続していきます。
Session Manager
踏み台サーバーへの接続にはSSHではなくSession Managerを使用します。
SSH接続のために鍵やネットワークの管理をする必要がなくなるのでより運用コストを下げることができます。
また、ポートを許可する必要もないのでセキュリティ面でのメリットもあります。
ローカルからの接続にはSession Manager Plugin
が必要なので入っていなければインストールします。
AWS CLI 用の Session Manager プラグインをインストールする
# Homebrewからインストールする場合
brew install --cask session-manager-plugin
踏み台サーバーに接続するだけであれば、copilot
のコマンド経由でコンテナに入れます。
copilot svc exec --app example --env dev --name bastion
なのでコンテナに入ることだけが目的であればここまでで実現できます。
今回はポートフォワーディングが目的なのでもう少し進めます。
ポートフォワード
copilot svc exec
でポートフォワーディングするオプションはなさそうなのでAWS CLI
とSession Manager Plugin
を使用して接続します。
ひとまずSession Manager Plugin
経由でコンテナに入れるかを試します。
コンテナとの接続を開始するにはaws ssm start-session
コマンドを使用します。
--target
オブションの引数にはecs:<cluster_id>_<task>_<runtime_id>
で接続先のコンテナを指定します。
aws ssm start-session \
--target ecs:<cluster_name>_<task_id>_<runtime_id>
cluster_idの取得
copilot env deploy --name dev
でenvをデプロイしていると、cluster_id
がCloudFormationでexportされているので下記で取得できます。
aws cloudformation list-exports | jq -r '.Exports[] | select(.Name == "example-dev-ClusterId").Value'
task_idの取得
copilot svc status -a example -e dev -n bastion --json | jq -r '.tasks[].id'
runtime_idの取得
aws ecs describe-tasks \
--cluster <cluster_id> \
--tasks <task_id> \
| jq -r '.tasks[].containers[].runtimeId'
上記でコンテナに入れることを確認できたと思いますので続けてポートフォワードを試します。
ポートフォワーディングする際のオプションはこちらを参考に、--document-name
と--parameters
を指定します。
aws ssm start-session \
--target ecs:<cluster_name>_<task_id>_<runtime_id> \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host": ["<RDBのエンドポイント>"], "portNumber": ["<RDBのポート番号>"], "localPortNumber": ["<ローカルのポート番号>"]}'
これで接続が開始されたと思うので、あとはローカルからAWS上のDBへ接続を試します。
mysql -h 127.0.0.1 -u user -P <ローカルのポート番号> -p
mysql>
最後に
以上、サーバーレスな踏み台サーバーを構築する方法を紹介しました。
どなたかの参考になれば幸いです。
あと、bastion
の発音は「バスチャン」らしいです。