AWS CDKで作るEC2(2) ー AWS CDKでSSHレスなUbuntu EC2をSSM経由で構築し、dev/prod環境を切り替える
の続き
現状の確認
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
interface Ec2StackProps extends cdk.StackProps {
vpc: ec2.IVpc;
}
export class Ec2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props: Ec2StackProps) {
super(scope, id, props);
const instance = new ec2.Instance(this, 'WebServer', {
vpc: props.vpc,
instanceType: new ec2.InstanceType('t3.micro'),
machineImage: ec2.MachineImage.latestAmazonLinux2023(),
keyName: 'your-keypair',
});
instance.connections.allowFromAnyIpv4(ec2.Port.tcp(22), 'SSH');
instance.connections.allowFromAnyIpv4(ec2.Port.tcp(80), 'HTTP');
}
}
このようにキーペアを設定してssh可能なホストを用意しているが、ここでsshを塞ぎamazonのコンソールで作業可能のようにする。これはSSM Session Manager
を利用する
SSM Session Managerを利用するためには...
- IAMロール作成 → 信頼ポリシー:
ec2.amazonaws.com
- アタッチポリシー:
AmazonSSMManagedInstanceCore
- EC2起動時にそのロールをインスタンスプロファイルとして付与
など、まあまあ深い設定を施す必要があるのだが、これをCDK
にやらせるというのが今回の趣旨だ。またAmazonLinux
からUbuntu
に変更するみたいなこともやってみよう。
現状のソースコード管理
今は https://github.com/catatsumuri/cdktest において全てmain
で行っている
これに対する変更なども行ってみよう
feature/no-ssh-ec2 ブランチを作成する
今回はgitで管理しているのだから小変更でもbranchを作るべきだ。
git checkout -b feature/no-ssh-ec2
ubuntuのAMIを取る
これはcloudshellで行うとよい。詳細は割愛するが以下のコマンドでubuntu22/amd64のIDを取得する
AMI_ID=$(
aws ec2 describe-images \
--region ap-northeast-1 \
--owners 099720109477 \
--filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
--query 'sort_by(Images, &CreationDate)[-1].ImageId' \
--output text
)
echo "$AMI_ID"
AMI IDami-01ff1fcabf5f7572c
が取れた
AMI IDを利用し、イメージを修正
ここでec2を修正
@@ -1,6 +1,7 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
+import * as iam from 'aws-cdk-lib/aws-iam';
interface Ec2StackProps extends cdk.StackProps {
vpc: ec2.IVpc;
@@ -10,14 +11,24 @@ export class Ec2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props: Ec2StackProps) {
super(scope, id, props);
+ const role = new iam.Role(this, 'WebServerRole', {
+ assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
+ });
+ role.addManagedPolicy(
+ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
+ );
+
+ const ami = ec2.MachineImage.genericLinux({
+ 'ap-northeast-1': 'ami-01ff1fcabf5f7572c',
+ });
+
const instance = new ec2.Instance(this, 'WebServer', {
vpc: props.vpc,
instanceType: new ec2.InstanceType('t3.micro'),
- machineImage: ec2.MachineImage.latestAmazonLinux2023(),
- keyName: 'your-keypair',
+ machineImage: ami,
+ role,
});
- instance.connections.allowFromAnyIpv4(ec2.Port.tcp(22), 'SSH');
instance.connections.allowFromAnyIpv4(ec2.Port.tcp(80), 'HTTP');
}
}
AmazonSSMManagedInstanceCore
はAWSが用意しているポリシーで、
- SSM AgentがSSMサービスと通信するための権限
- CloudWatch LogsやS3などSSM機能で使う基本的な権限
がまとめて入っている。さらに
const ami = ec2.MachineImage.genericLinux({
'ap-northeast-1': 'ami-01ff1fcabf5f7572c', // これと
});
const instance = new ec2.Instance(this, 'WebServer', {
vpc: props.vpc,
instanceType: new ec2.InstanceType('t3.micro'),
machineImage: ami, // <-ここ
role,
});
さらにここでUbuntuのAMI ID ami-01ff1fcabf5f7572c
に差し替えている
適用
git add lib/ec2-stack.ts
git commit -m "EC2: switch AMI to fixed ID ami-01ff1fcabf5f7572c; remove SSH; add SSM role"
git push -u origin feature/no-ssh-ec2
プルリクエスト
github使ってるならPRもしますか。gh
コマンドが使えるという前提で
gh pr create \
--base main \
--head feature/no-ssh-ec2 \
--title "EC2: switch AMI to fixed ID ami-01ff1fcabf5f7572c; remove SSH; add SSM role" \
--body "Switch AMI to ami-01ff1fcabf5f7572c for ap-northeast-1; remove SSH access; add SSM Session Manager role."
ができる。あらかた確認してMerge。cliで一括ブランチ削除とかまでやるなら
gh pr merge feature/no-ssh-ec2 --merge --delete-branch
でもいいかも
マージされた
本番環境に適用
ここからはcloudshellで
cd cdktest
git pull origin main
ここでは前回の記事通りだとcdk destroy
で掃除しちゃってるのでcdk diff
がちょっとうるさいが、cdk diffした後
cdk deploy --all
確認
IAMロールから
AmazonSSMManagedInstanceCore
これが付いている事を確認する。
ログインするには
AWS Systems Managerにアクセス https://ap-northeast-1.console.aws.amazon.com/systems-manager/home?region=ap-northeast-1
`セッションマネージャー
セッションを開始する
以上のように遷移すると
Ec2Stack/WebServer
がターゲットされている
このように当該インスタンスが出てくるのでセッションを開始
からログインできる
apt update
を実行してみた結果
EC2デプロイと同時にwebサーバーも起動するようにする
前回は
sudo dnf update -y
sudo dnf install -y httpd
sudo systemctl enable httpd
sudo systemctl start httpd
と手入力してwebサーバーを起動したが、これをuserdataに持っていくという手法がある。ともあれ、やってみよう。ただし、dnf
はredhat系のコマンドなので今回ubuntuに切り替えたことによりapt
に変更する必要がある。
@@ -22,11 +22,20 @@ export class Ec2Stack extends cdk.Stack {
'ap-northeast-1': 'ami-01ff1fcabf5f7572c',
});
+ const userData = ec2.UserData.forLinux();
+ userData.addCommands(
+ 'export DEBIAN_FRONTEND=noninteractive',
+ 'apt-get update -y',
+ 'apt-get install -y apache2',
+ 'systemctl enable apache2',
+ 'systemctl start apache2',
+ );
+
const instance = new ec2.Instance(this, 'WebServer', {
vpc: props.vpc,
instanceType: new ec2.InstanceType('t3.micro'),
machineImage: ami,
role,
+ userData,
});
instance.connections.allowFromAnyIpv4(ec2.Port.tcp(80), 'HTTP');
そしたら
git checkout -b feature/apache2-userdata
git add lib/ec2-stack.ts
git commit -m "EC2: add UserData to install & start Apache2"
git push -u origin feature/apache2-userdata
こんな感じにfeature/apache2-userdata
ブランチをpushして
gh pr create \
--base main \
--head feature/apache2-userdata \
--title "EC2: add UserData to install & start Apache2" \
--body "Install Apache2 via UserData and enable/start service at boot for EC2 instance in ap-northeast-1."
プルリクを出す。確認したら
gh pr merge feature/apache2-userdata --squash --delete-branch
とかのサイクルでやっていく
cloudshellで適用
pullしてcdk diff
Stack Ec2Stack
Resources
[~] AWS::EC2::Instance WebServer WebServer99EDD300 may be replaced
└─ [~] UserData (may cause replacement)
└─ [~] .Fn::Base64:
├─ [-] #!/bin/bash
└─ [+] #!/bin/bash
apt update -y
apt install -y apache2
systemctl enable apache2
systemctl start apache2
✨ Number of stacks with differences: 1
Ec2Stack
をdeploy
cdk deploy Ec2Stack
これでapache付きのEc2が起動してくる。
apache2が起動した
環境の切り分け
ここでEc2Stack
をdestroyするとEBSもろとも消滅する。これは実運用が初まったときはこのようにdestroyと同時に消滅していいとは思えない。ただ、開発環境のときは消滅していいということもあるだろう。ここで、IaC
を使う利点として本番用のprod
と開発用のdev
を分けたりというような運用が考えられるので、やってみよう。
ハンズオン的にやる場合は一度ここで全てdestroyしておくのを推奨
cdk destroy --all
cdk の -cオプション
ここで重要になるのはcdk
コマンドの -c オプションである。これは CDK
アプリケーションに任意のキーと値のペアを渡すための仕組みであり、AWS CDK ではこれを コンテキスト (Context) と呼ぶ。まずはこれを利用してみよう。
@@ -4,10 +4,18 @@ import { VpcStack } from '../lib/vpc-stack';
import { Ec2Stack } from '../lib/ec2-stack';
const app = new cdk.App();
-const vpcStack = new VpcStack(app, 'VpcStack', {
+const envName = app.node.tryGetContext('env') || 'dev';
+console.log('Deploying environment:', envName);
+
+// 共通タグ
+cdk.Tags.of(app).add('Env', envName);
+
+// スタック名にenvを含めてdev/prod共存可能に
+const vpcStack = new VpcStack(app, `VpcStack-${envName}`, {
+ envName,
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
-new Ec2Stack(app, 'Ec2Stack', {
+
+new Ec2Stack(app, `Ec2Stack-${envName}`, {
vpc: vpcStack.vpc,
- // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
ここで
const envName = app.node.tryGetContext('env') || 'dev';
によりenv
というキーを渡せばそれがenvName
に代入される。ここで
cdk.Tags.of(app).add('Env', envName);
この処理により「全てのリソースに」env=envName
がセットされる。たとえばenv
がprod
とかdev
とか
VpcStackの改造
VpcStack
に渡したので、そっちも改造する。とりあえずoutputするだけ
@@ -2,10 +2,14 @@ import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
+interface VpcStackProps extends cdk.StackProps {
+ envName: 'dev' | 'prod';
+}
+
export class VpcStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
- constructor(scope: Construct, id: string, props?: cdk.StackProps) {
+ constructor(scope: Construct, id: string, props: VpcStackProps) {
super(scope, id, props);
// NATなし、パブリックサブネットのみのVPC
@@ -20,5 +24,9 @@ export class VpcStack extends cdk.Stack {
},
],
});
+ new cdk.CfnOutput(this, 'CurrentEnv', {
+ value: props.envName,
+ description: 'Current environment name (dev or prod)',
+ });
}
}
環境付きdeploy
この段階でEC2をdeployするといろいろ面倒だからVPCを単独deployしてみよう。たとえば何も考えず --allでdeployすると
cdk deploy VpcStack-dev
fallbackでenvName
がdev
に落ちるのでこんな感じになる。
cdk.CfnOutputのcurrentEnvでdev
が出力されている
さらに
Name
タグにVPC名、Env
タグにdev
が含まれる
prodをdeployするには
cdk deploy -c env=prod VpcStack-prod
こんな感じになるわけだ
VPCが2本
スタックも2つ
cdk.json にコンテキストを定義する方法もあるが、まあ割愛。そのうち出てくるかも。
prodのVPCに追加候補の項目
これはVpcフローログ
とかが考えられるが、トレーニングを多少しないと取るだけで金の無駄になる可能性もあるので一応参考ポインタだけ
次回
この辺の環境設定も絡めながらEBSに関する深い話を...する...のか?
Discussion