この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
今回は ALB を構築します。
前回作成した EC2 をこの ALB に紐付けます。
前回の記事はこちら。
AWS 構成図
パブリックなサブネットに ALB を配置します。
設計
ALB、リスナー、ターゲットグループを作成します。
プロパティは以下の通り。
devio-stg-alb(ALB)
項目 | 値 |
---|---|
種類 | application |
スキーム | internet-facing |
IP アドレスタイプ | ipv4 |
サブネット | devio-stg-subnet-public-1a devio-stg-subnet-public-1c |
セキュリティグループ | devio-stg-sg-alb |
リスナー
項目 | 値 |
---|---|
リスナー ID | HTTP : 80 |
ルール | デフォルト: 転送先 devio-stg-tg |
devio-stg-tg(ターゲットグループ)
項目 | 値 |
---|---|
ターゲットタイプ | Instance |
プロトコル | HTTP |
ポート | 80 |
VPC | devio-stg-vpc |
ターゲット | devio-stg-ec2-1a devio-stg-ec2-1c |
実装
ALB に関する処理を行うクラスはこちら。
lib/resource/alb.ts
import * as cdk from '@aws-cdk/core';
import { CfnLoadBalancer, CfnTargetGroup, CfnListener } from '@aws-cdk/aws-elasticloadbalancingv2';
import { CfnVPC, CfnSubnet, CfnSecurityGroup, CfnInstance } from '@aws-cdk/aws-ec2';
import { Resource } from './abstract/resource';
export class Alb extends Resource {
public loadBalancer: CfnLoadBalancer;
private readonly vpc: CfnVPC;
private readonly subnetPublic1a: CfnSubnet;
private readonly subnetPublic1c: CfnSubnet;
private readonly securityGroupAlb: CfnSecurityGroup;
private readonly ec2Instance1a: CfnInstance;
private readonly ec2Instance1c: CfnInstance;
constructor(
vpc: CfnVPC,
subnetPublic1a: CfnSubnet,
subnetPublic1c: CfnSubnet,
securityGroupAlb: CfnSecurityGroup,
ec2Instance1a: CfnInstance,
ec2Instance1c: CfnInstance
) {
super();
this.vpc = vpc;
this.subnetPublic1a = subnetPublic1a;
this.subnetPublic1c = subnetPublic1c;
this.securityGroupAlb = securityGroupAlb;
this.ec2Instance1a = ec2Instance1a;
this.ec2Instance1c = ec2Instance1c;
};
createResources(scope: cdk.Construct) {
this.loadBalancer = this.createLoadBalancer(scope);
const targetGroup = this.createTargetGroup(scope);
this.createListener(scope, this.loadBalancer, targetGroup);
}
private createLoadBalancer(scope: cdk.Construct): CfnLoadBalancer {
const loadBalancer = new CfnLoadBalancer(scope, 'Alb', {
ipAddressType: 'ipv4',
name: this.createResourceName(scope, 'alb'),
scheme: 'internet-facing',
securityGroups: [this.securityGroupAlb.attrGroupId],
subnets: [this.subnetPublic1a.ref, this.subnetPublic1c.ref],
type: 'application'
});
return loadBalancer;
}
private createTargetGroup(scope: cdk.Construct): CfnTargetGroup {
const targetGroup = new CfnTargetGroup(scope, 'AlbTargetGroup', {
name: this.createResourceName(scope, 'tg'),
port: 80,
protocol: 'HTTP',
targetType: 'instance',
targets: [
{
id: this.ec2Instance1a.ref
},
{
id: this.ec2Instance1c.ref
}
],
vpcId: this.vpc.ref
});
return targetGroup;
}
private createListener(scope: cdk.Construct, loadBalancer: CfnLoadBalancer, targetGroup: CfnTargetGroup) {
new CfnListener(scope, 'AlbListener', {
defaultActions: [{
type: 'forward',
forwardConfig: {
targetGroups: [{
targetGroupArn: targetGroup.ref,
weight: 1
}]
}
}],
loadBalancerArn: loadBalancer.ref,
port: 80,
protocol: 'HTTP'
});
}
}
まずは ALB に関する Construct を利用するために @aws-cdk/aws-elasticloadbalancingv2
をインストールします。
$ npm install @aws-cdk/aws-elasticloadbalancingv2
@aws-cdk/aws-elasticloadbalancing
と間違えないようにご注意ください。末尾に v2
が付きます。
今回は各リソース 1 つずつなので ResourceInfo
のインタフェースは作成しません。
それぞれリソースを作成するための createXxx()
メソッドを用意し、その中でプロパティを直接設定しています。
また、動作確認をしやすいように EC2 に Apache をインストールするユーザーデータを設定しました。これで EC2 インスタンス起動時に自動的に Apache がインストールされ、Web ブラウザから ALB にアクセスすると Apache のテストページが表示されます。
lib/script/ec2/userData.sh
#!/bin/bash
sudo yum -y install httpd
sudo systemctl enable httpd
sudo systemctl start httpd
lib/resource/ec2.ts
import * as fs from 'fs';
~ 省略 ~
private static readonly userDataFilePath = `${__dirname}/../script/ec2/userData.sh`;
~ 省略 ~
private createInstance(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnInstance {
const instance = new CfnInstance(scope, resourceInfo.id, {
availabilityZone: resourceInfo.availabilityZone,
iamInstanceProfile: this.instanceProfileEc2.ref,
imageId: Ec2.latestImageIdAmazonLinux2,
instanceType: Ec2.instanceType,
securityGroupIds: [this.securityGroupEc2.attrGroupId],
subnetId: resourceInfo.subnetId(),
tags: [{
key: 'Name',
value: this.createResourceName(scope, resourceInfo.resourceName)
}],
userData: fs.readFileSync(Ec2.userDataFilePath, 'base64')
});
return instance;
}
ハイライト部分を追加しました。
やっていることは次の通りです。
- ユーザーデータのスクリプトを別ファイルに分離
- EC2 インスタンス作成時のプロパティに
userData
を追加 - ファイルから読み込むため
fs
モジュールのreadFileSync()
メソッドを利用(エンコーディングは Base64 を指定)- CFn でユーザーデータを記述する場合は Base64 でエンコードしなければならない
- [参考]:UserData | AWS CloudFormation User Guide
- ユーザーデータのファイルパスは定数化
__dirname
はカレントディレクトリの絶対パス- [参考]:__dirname | Node.js documentation
メインのプログラムはこちら。
ハイライト部分を追記しました。
lib/devio-stack.ts
import * as cdk from '@aws-cdk/core';
import { Vpc } from './resource/vpc';
import { Subnet } from './resource/subnet';
import { InternetGateway } from './resource/internetGateway';
import { ElasticIp } from './resource/elasticIp';
import { NatGateway } from './resource/natGateway';
import { RouteTable } from './resource/routeTable';
import { NetworkAcl } from './resource/networkAcl';
import { IamRole } from './resource/iamRole';
import { SecurityGroup } from './resource/securityGroup';
import { Ec2 } from './resource/ec2';
import { Alb } from './resource/alb';
export class DevioStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC
const vpc = new Vpc();
vpc.createResources(this);
~ 省略 ~
// ALB
const alb = new Alb(
vpc.vpc,
subnet.public1a,
subnet.public1c,
securityGroup.alb,
ec2.instance1a,
ec2.instance1c
);
alb.createResources(this);
}
}
テスト
テストコードはこちら。
test/resource/alb.test.ts
import { expect, countResources, haveResource, anything } from '@aws-cdk/assert';
import * as cdk from '@aws-cdk/core';
import * as Devio from '../../lib/devio-stack';
test('Alb', () => {
const app = new cdk.App();
const stack = new Devio.DevioStack(app, 'DevioStack');
expect(stack).to(countResources('AWS::ElasticLoadBalancingV2::LoadBalancer', 1));
expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', {
IpAddressType: 'ipv4',
Name: 'undefined-undefined-alb',
Scheme: 'internet-facing',
SecurityGroups: anything(),
Subnets: anything(),
Type: 'application'
}));
expect(stack).to(countResources('AWS::ElasticLoadBalancingV2::TargetGroup', 1));
expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', {
Name: 'undefined-undefined-tg',
Port: 80,
Protocol: 'HTTP',
TargetType: 'instance',
Targets: anything(),
VpcId: anything()
}));
expect(stack).to(countResources('AWS::ElasticLoadBalancingV2::Listener', 1));
expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', {
DefaultActions: [{
Type: 'forward',
ForwardConfig: {
TargetGroups: [{
TargetGroupArn: anything(),
Weight: 1
}]
}
}],
LoadBalancerArn: anything(),
Port: 80,
Protocol: 'HTTP'
}));
});
以下を確認しています。
- ALB のリソースが 1 つあること
- ターゲットグループのリソースが 1 つあること
- リスナーのリソースが 1 つあること
- 各リソースのプロパティが正しいこと
確認
マネジメントコンソール上でリソースを確認してみましょう。
ALB が正しく作成されています。
お次はリスナー。
こちらもデフォルトルールでターゲットグループへ転送されるようになっていますね。
ターゲットグループも指定したインスタンスが登録されています。
Health status が unhealthy
になっていますが、これはインスタンス内に /var/www/html/index.html
を作成することで解消されます。(Apache のテストページを表示したいのでこのままにします)
そしてこちらがそのテストページです。
ALB のドメイン(ここでは devio-stg-alb-1622770308.ap-northeast-1.elb.amazonaws.com
)にアクセスすることで表示されます。
これで ALB に対する通信が EC2 までルーティングされていることがわかりますね。
これにて確認完了です!
CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
Alb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
Name: devio-stg-alb
Scheme: internet-facing
SecurityGroups:
- Fn::GetAtt:
- SecurityGroupAlb
- GroupId
Subnets:
- Ref: SubnetPublic1a
- Ref: SubnetPublic1c
Type: application
AlbTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: devio-stg-tg
Port: 80
Protocol: HTTP
Targets:
- Id:
Ref: Ec2Instance1a
- Id:
Ref: Ec2Instance1c
TargetType: instance
VpcId:
Ref: Vpc
AlbListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- ForwardConfig:
TargetGroups:
- TargetGroupArn:
Ref: AlbTargetGroup
Weight: 1
Type: forward
LoadBalancerArn:
Ref: Alb
Port: 80
Protocol: HTTP
なお、EC2 の UserData に関しては Base64 でエンコードしたので、AWS CDK からのテンプレート出力では次のように表示されてしまいます。
Ec2Instance1a:
Type: AWS::EC2::Instance
Properties:
AvailabilityZone: ap-northeast-1a
IamInstanceProfile:
Ref: InstanceProfileEc2
ImageId: ami-06631ebafb3ae5d34
InstanceType: t2.micro
SecurityGroupIds:
- Fn::GetAtt:
- SecurityGroupEc2
- GroupId
SubnetId:
Ref: SubnetApp1a
Tags:
- Key: Name
Value: devio-stg-ec2-1a
UserData: IyEvYmluL2Jhc2gKc3VkbyB5dW0gLXkgaW5zdGFsbCBodHRwZApzdWRvIHN5c3RlbWN0bCBlbmFibGUgaHR0cGQKc3VkbyBzeXN0ZW1jdGwgc3RhcnQgaHR0cGQK
このタイミングで中身が見れないのは残念ですが、確認したいときはファイルを見に行くようにしましょう。
GitHub
今回のソースコードは コチラ です。
おわりに
ALB を設置することによってインターネット経由のアクセスで動作確認ができるようになりました。
マネジメントコンソール以外で確認できるのは嬉しいですね。ようやく 構築できてるんだな ということが実感できます。
ではゴールまでもう少し、次回からは DB まわりを構築していきましょう。
リンク
- class CfnLoadBalancer (construct) | AWS CDK API Reference
- class CfnTargetGroup (construct) | AWS CDK API Reference
- class CfnListener (construct) | AWS CDK API Reference
- AWS::ElasticLoadBalancingV2::LoadBalancer | AWS CloudFormation User Guide
- AWS::ElasticLoadBalancingV2::TargetGroup | AWS CloudFormation User Guide
- AWS::ElasticLoadBalancingV2::Listener | AWS CloudFormation User Guide
- AWS::EC2::Instance | AWS CloudFormation User Guide
- Application Load Balancer とは? | Elastic Load Balancing
- Application Load Balancer | Elastic Load Balancing
- Application Load Balancer のリスナー | Elastic Load Balancing
- Application Load Balancer のターゲットグループ | Elastic Load Balancing
- 起動時に Linux インスタンスでコマンドを実行する | Amazon EC2 User Guide
- Compiling and Installing | The Apache HTTP Server Project
- Modules: CommonJS modules | Node.js documentation