PulumiでInfrastructure as Code
インフラストラクチャの自動化は、現在のソフトウェア開発において不可欠な要素となっています。今回は、Infrastucture as CodeのツールであるPulumiについて解説し、実際の使用例を紹介します。
Pulumiとは
Pulumiは、TypeScriptやPythonなどのプログラミング言語を使用して、インフラを管理・デプロイできるツールです。
AWS CDKに似ていますが、PulumiはAWSだけでなく、AzureやGoogle Cloudにも対応しています。この点では、Terraformと同様ですね。
また、Pulumiはオープンソースで開発されています。細かい挙動を確認したい場合は、ソースコードレベルで確認することができます。
Pulumiのプロジェクト作成
今回は、Homebrewを使用して、Pulumiをインストールします。
$ brew install pulumi/tap/pulumi
インストールができたら、プロジェクトを作成します。初回実行の場合、Pulumi Cloudのアカウント作成(無料)が求められるので、作成します
$ mkdir quickstart && cd quickstart
$ pulumi new aws-typescript
構築するAWS構成の概要
今回は、以下のような構成で構築します。
- VPC: プライベートなネットワーク環境を構築します。サブネットを複数作成し、冗長構成にします。
- Application Load Balancer(ALB): ブラウザからのリクエストを受け取るエンドポイントを構築します。リクエストを受け取った場合、Webサーバに転送します。
- Fargate: サーバレスコンテナ実行環境を構築します。Nginxが動作しているWebサーバコンテナを起動します。
構築した後にブラウザでアクセスすると、以下のようなNginxのデフォルトページが表示されるようになります。
VPCの構築
index.ts を開いて、以下のコードを記述します。
// VPCの作成
const vpc = new awsx.ec2.Vpc("vpc", {
numberOfAvailabilityZones: 2, // AZを2つ使用する(デフォルトは3つ)
});
これだけの記述ですが、以下のようなVPC関連のリソースが展開されます。
- 10.0.0.0/16のCIDRブロックを持つVPCを作成
- 2つのAZを使用
- 1つのAZに対して、パブリックサブネットとプライベートサブネットを作成
- 1つのプライベートサブネットに対して、NATゲートウェイとEIPを作成
- すべてのパブリックサブネットで共通で使用するインターネットゲートウェイを作成
- Route Tableの関連付け
- NACLの設定
今回は、ほぼデフォルトの設定で済ませてしまいましたが、必要に応じて、引数を渡すことによってカスタマイズすることが可能です。詳細は、以下のページを参照してください。
https://www.pulumi.com/docs/clouds/aws/guides/vpc/
ALBとセキュリティグループの構築
次に、ALBとセキュリティグループの設定を行うために、以下のようなコードを追記します。
// ①ALB用のセキュリティグループ
const lbSg = new aws.ec2.SecurityGroup("lbSg", {
vpcId: vpc.vpcId,
ingress: [
{
fromPort: 0,
toPort: 0,
protocol: "-1",
cidrBlocks: ["0.0.0.0/0"],
},
],
});
// ②ECS用のセキュリティグループ
const ecsSg = new aws.ec2.SecurityGroup("ecsSg", {
vpcId: vpc.vpcId,
egress: [
{
fromPort: 0,
toPort: 0,
protocol: "-1",
cidrBlocks: ["0.0.0.0/0"],
ipv6CidrBlocks: ["::/0"],
},
],
ingress: [
// ALBからのアクセスを許可
{
securityGroups: [lbSg.id],
fromPort: 80,
toPort: 80,
protocol: "tcp",
}
]
});
// ③ALB
const lb = new awsx.lb.ApplicationLoadBalancer("lb", {
listener: {
port: 80,
},
subnetIds: vpc.publicSubnetIds,
securityGroups: [lbSg.id],
});
- ALB用のセキュリティグループを作成し、インターネットゲートウェイからの通信を許可する(①)
- ECS(Fargate)用のセキュリティグループを作成し、ALB用のセキュリティグループからの通信を許可する(②)
- ALBを作成し、パブリックサブネットに配置し、ALB用のセキュリティグループに紐づける(③)
これにより、Fargateで作成されたコンテナとALBで通信をする環境が整いました。
Fargateの設定
次に、Fargateの設定を行うために、以下のようなコードを追記します。
// ①クラスタを作成
const cluster = new aws.ecs.Cluster("cluster", {});
// Fargateサービス
const service = new awsx.ecs.FargateService("service", {
cluster: cluster.arn,
networkConfiguration: {
subnets: vpc.privateSubnetIds, // ②プライベートサブネットに配置
securityGroups: [ecsSg.id], // ③ECS用のセキュリティグループを指定
},
desiredCount: 2,
taskDefinitionArgs: {
container: {
name: "my-service",
image: "nginx:latest", // ④Nginxを使用
cpu: 128,
memory: 512,
essential: true,
portMappings: [
{
containerPort: 80,
targetGroup: lb.defaultTargetGroup, // ⑤ALBのターゲットグループを指定
},
],
},
},
});
- クラスタとサービスを作成(①)
- コンテナをプライベートサブネットに配置(②)
- コンテナをECS用セキュリティグループに配置(③)
- NginxのDockerイメージを使用するように設定(④)
- ALBのターゲットグループに配置(⑤)
これにより、コンテナはプライベートサブネットで動作するようになり、リクエストはALB経由から受け取るようになりました。
リソース情報を出力する
リソースの情報を出力するように、以下のようなコードを追記します。
export const vpcId = vpc.vpcId; // VPC ID
export const privateSubnetIds = vpc.privateSubnetIds; // プライベートサブネットID
export const publicSubnetIds = vpc.publicSubnetIds; // パブリックサブネットID
export const url = pulumi.interpolate`http://${lb.loadBalancer.dnsName}`; // ALBのDNS名
これにより、デプロイされたリソースの情報が出力されるようになりました。特に、ALBのDNS名は、ブラウザでアクセスする際に必要となる情報なので、出力するように設定しておくと良いです。
デプロイする
実際にAWS環境にデプロイします。AWSにデプロイするための設定が事前に必要となります。
以下のコマンドでデプロイします。
$ pulumi up
デプロイが成功すると、以下のようなリソース情報が出力されます。
Outputs:
privateSubnetIds: [
[0]: "subnet-01e26ac603118833e"
[1]: "subnet-036a90672fc40add1"
]
publicSubnetIds : [
[0]: "subnet-0ebc910465d019c2a"
[1]: "subnet-0f8b6f6fb35320fcb"
]
url : "http://lb-27401a6-1760559541.ap-northeast-1.elb.amazonaws.com"
vpcId : "vpc-071bdb8260ed4a453"
urlにある値をブラウザのURL欄に入力してアクセスするとNginxのデフォルトページが表示されます。
削除する
$ pulumi down
全体像
今回作成した全体的な構成図とソースコードを載せておきます。
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
// VPCの作成
const vpc = new awsx.ec2.Vpc("vpc", {
numberOfAvailabilityZones: 2,
});
// ALB用のセキュリティグループ
const lbSg = new aws.ec2.SecurityGroup("lbSg", {
vpcId: vpc.vpcId,
ingress: [
{
fromPort: 0,
toPort: 0,
protocol: "-1",
cidrBlocks: ["0.0.0.0/0"],
},
],
});
// ECS用のセキュリティグループ
const ecsSg = new aws.ec2.SecurityGroup("ecsSg", {
vpcId: vpc.vpcId,
egress: [
{
fromPort: 0,
toPort: 0,
protocol: "-1",
cidrBlocks: ["0.0.0.0/0"],
ipv6CidrBlocks: ["::/0"],
},
],
ingress: [
// ALBからのアクセスを許可
{
securityGroups: [lbSg.id],
fromPort: 80,
toPort: 80,
protocol: "tcp",
}
]
});
// ALB
const lb = new awsx.lb.ApplicationLoadBalancer("lb", {
listener: {
port: 80,
},
subnetIds: vpc.publicSubnetIds,
securityGroups: [lbSg.id],
});
// クラスタ
const cluster = new aws.ecs.Cluster("cluster", {});
// Fargateサービス
const service = new awsx.ecs.FargateService("service", {
cluster: cluster.arn,
networkConfiguration: {
subnets: vpc.privateSubnetIds, // ①プライベートサブネットに配置
securityGroups: [ecsSg.id], // ②ECS用のセキュリティグループを指定
},
desiredCount: 2,
taskDefinitionArgs: {
container: {
name: "my-service",
image: "nginx:latest", // ③Nginxを使用
cpu: 128,
memory: 512,
essential: true,
portMappings: [
{
containerPort: 80,
targetGroup: lb.defaultTargetGroup, // ④ALBのターゲットグループを指定
},
],
},
},
});
export const vpcId = vpc.vpcId; // VPC ID
export const privateSubnetIds = vpc.privateSubnetIds; // プライベートサブネットID
export const publicSubnetIds = vpc.publicSubnetIds; // パブリックサブネットID
export const url = pulumi.interpolate`http://${lb.loadBalancer.dnsName}`; // ALBのDNS名
おわりに
今回は、IaCツールであるPulumiを試してみました。
Pulumiは、今回の記事では取り上げなかった様々な機能を備えています。その中でも注目なのが、Pulumi AIです。自然言語で要望を出せば、ソースコードを出力してくれるAIサービスです。今回は触れませんでしたが、また次回ご紹介したいと思います。