Redshift Serverlessを立ち上げるためのVPC付きのCloudFormationのテンプレートを作ってみた
はじめに
好物はインフラとフロントエンドのかじわらゆたかです。 Redshift Serverless熱いですね。
GAされて以後、様々なブログがDevelopers.IOでも出ているのもあり、 気軽に試せるように環境構築用のCloudFormationのテンプレートを書いてみました。
書いてみた
AWSTemplateFormatVersion: "2010-09-09" Description: "Redshift Serverless and VPC" Parameters: Env: Type: "String" Default: "test" ProjectName: Type: "String" CidrBlock: Description: Please type the CidrBlock. Type: String Default: 192.168.0.0/22 BaseCapacity: Type: Number Default: 32 EnhancedVpcRouting: Type: String AllowedValues: - true - false Default: false PubliclyAccessible: Type: String AllowedValues: - true - false Default: true AdminUsername: Type: String Default: awsuser AdminUserPassword: Type: String Description: Must be 8-64 characters long. Must contain at least one uppercase letter, one lowercase letter and one number. Can be any printable ASCII character except “/”, ““”, or “@”. NoEcho: true MinLength: 8 MaxLength: 64 KmsKeyId: Type: String Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Sub ${CidrBlock} EnableDnsSupport: True EnableDnsHostnames: True InstanceTenancy: default Tags: - Key: Name Value: !Sub ${ProjectName}-redshiftserverless-${Env}-VPC InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Application Value: Ref: AWS::StackId - Key: Network Value: Public AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: Ref: VPC InternetGatewayId: Ref: InternetGateway PublicRouteTable: Type: AWS::EC2::RouteTable DependsOn: AttachGateway Properties: VpcId: Ref: VPC Tags: - Key: Name Value: !Sub | ${ProjectName}-redshiftserverless-${Env}-public-rtb - Key: Application Value: Ref: AWS::StackId PublicRoute: Type: AWS::EC2::Route DependsOn: AttachGateway Properties: RouteTableId: Ref: PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: InternetGateway Subnet1: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC AvailabilityZone: !Select [ 0, !GetAZs ] CidrBlock: !Select [ 0, !Cidr [ !GetAtt VPC.CidrBlock, 4, 8 ]] Subnet2: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC AvailabilityZone: !Select [ 1, !GetAZs ] CidrBlock: !Select [ 1, !Cidr [ !GetAtt VPC.CidrBlock, 4, 8 ]] Subnet3: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC AvailabilityZone: !Select [ 2, !GetAZs ] CidrBlock: !Select [ 2, !Cidr [ !GetAtt VPC.CidrBlock, 4, 8 ]] Subnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: Ref: Subnet1 RouteTableId: Ref: PublicRouteTable Subnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: Ref: Subnet2 RouteTableId: Ref: PublicRouteTable Subnet3RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: Ref: Subnet3 RouteTableId: Ref: PublicRouteTable RedshiftServerlessSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: Ref: VPC GroupDescription: Marker security group for Application server. Tags: - Key: Name Value: !Sub | ${ProjectName}-redshiftserverless-${Env}-sg RedshiftServerlessRole: Type: "AWS::IAM::Role" Properties: Path: "/" RoleName: !Sub "${ProjectName}-${Env}-redshift-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - redshift.amazonaws.com Action: sts:AssumeRole MaxSessionDuration: 3600 ManagedPolicyArns: - "arn:aws:iam::aws:policy/AmazonAthenaFullAccess" - "arn:aws:iam::aws:policy/AmazonS3FullAccess" - "arn:aws:iam::aws:policy/AWSGlueConsoleFullAccess" - "arn:aws:iam::aws:policy/AmazonRedshiftAllCommandsFullAccess" Description: "Allows Redshift clusters to call AWS services on your behalf." Tags: - Key: "Name" Value: !Sub "${ProjectName}-redshiftserverless-${Env}-redshift-role" RedshiftServerlessWorkGroup: Type: AWS::RedshiftServerless::Workgroup Properties: WorkgroupName: !Sub "${ProjectName}-${Env}-redshift-wg" BaseCapacity: !Ref BaseCapacity EnhancedVpcRouting: !Ref EnhancedVpcRouting NamespaceName: !Ref RedshiftServerlessNamespace PubliclyAccessible: !Ref PubliclyAccessible SecurityGroupIds: - !Ref RedshiftServerlessSecurityGroup SubnetIds: - !Ref Subnet1 - !Ref Subnet2 - !Ref Subnet3 RedshiftServerlessNamespace: Type: AWS::RedshiftServerless::Namespace Properties: NamespaceName: !Sub "${ProjectName}-${Env}-redshift-ns" AdminUsername: !Ref AdminUsername AdminUserPassword: !Ref AdminUserPassword KmsKeyId: !Ref KmsKeyId DbName: !Sub "${ProjectName}-db" IamRoles: - !GetAtt RedshiftServerlessRole.Arn
#!/bin/bash set -eu export AWS_PAGER="" CHANGESET_OPTION="--no-execute-changeset" # 実行時に指定された引数の数、つまり変数 $# の値が 4 でなければエラー終了。 if [ $# -ne 4 ]; then echo "指定された引数は$#個です。" 1>&2 echo "実行するには3個の引数が必要です。" 1>&2 echo "$0 [deploy or check] [ProjectName] [AdminUserPassword Parameter Store Key ][AWS CLI Profile]" exit 1 fi # 第1引数がdeployの場合、Change setの確認は行わず、Deployを行います。 if [ $1 = "deploy" ]; then echo "deploy mode" CHANGESET_OPTION="" else echo "check mode" fi # 第2引数にはプロジェクト名を指定します。 ProjectName=$2 # 第3引数にはRedshift Serverlessのパスワードが格納されているパラメータストアのキーを指定します。 AdminUserPasswordKey=$3 # 第4引数にはAWS CLIのProfileを指定します。 profile=$4 # パラメータストアから格納されているパスワードの値を取得します。 AdminUserPassword=`aws ssm get-parameter --name ${AdminUserPasswordKey} --profile $profile --with-decryption --query Parameter.Value --output text` # RedshiftのAWS マネージドキーのIDを取得します。別の鍵を使う場合は個々の値を書き換えてください。 KmsKeyID=$(aws kms list-aliases --output text --profile $profile --query 'Aliases[?AliasName == `alias/aws/redshift`].TargetKeyId') CFN_TEMPLATE=./Redshift_Serverless.yml CFN_STACK_NAME=${ProjectName}-redshiftserverless # テンプレートの実行 aws cloudformation deploy --stack-name ${CFN_STACK_NAME} --template-file ${CFN_TEMPLATE} ${CHANGESET_OPTION} \ --profile $profile \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides \ ProjectName=$ProjectName \ AdminUserPassword=$AdminUserPassword \ KmsKeyId=$KmsKeyID
CloudFormationとシェルで分割している理由
CloudFormationをAWS CLIから動かすのが便利だということをハマコーの記事から学んだので、それを使いたかったのと、 それ以外にも以下の理由があります。
KMSのIDはアカウントごとに固有だがCloudFormationから取得する方法がない
Redshift ServerlessはKMSによる暗号化が必須で、構築時には用いるKMSのIDを入力する必要があります。
この値は動かすAWSアカウントごとに固有になるためCloudFormationのテンプレートに記載するのではなく、CloudFormation単体で取得したいのですが取得することができません。
そのため、シェルの中でAWS マネージドキーのID取得してCloudFormationのパラメーターとして渡しています。
Redshift ServerlessのAdminUserPasswordはSecureString未対応
Cloudformationではパスワード等の文字列をパラメータストアのSecureStringから取得すると言った方法があります。 ServerlessではないRedshiftの場合SecureStringで格納した値をMasterUserPasswordに格納すると言った事が可能なのですが、 RedshiftServerlessは対応しておらずパラメータストアから取得しようとするためにはSecureStringではなく、普通のStringとしてパラメータストアに格納しそれを参照すると言った方法を取る必要があります。(2022/09現在)
動的な参照を使用してテンプレート値を指定する - AWS CloudFormation
パスワードという機密データをテンプレートに埋め込むのはもちろん論外で、パラメータストアに只の文字列として格納するのも微妙です。
そのためSecureStringとしてパラメータストアに格納し、その値の取得をシェルから行いCloudFormationにわたすと言った方法をとっています。
もちろん値はNoEcho: true
と設定しているので、Cloudformationのマネージメントコンソール上からの確認はできません。
VPC構築を少しスマートに書いてみました。
Redshift Serverlessを構築する場合、Availability Zoneは3つにまたいだSubnetが必要です。 また、Redshift Serverlessに割り当てるBaseUnitCapacityの数によっては十分なIPも必要です。
VPCを新しく作成した際にSubnetに割り当てるCIDRの計算やAZの文字列を組み合わせたりしたくないなって思っていたのですが、そこらへんはもうしなくてよさそうです。
RegionごとのAvailability Zoneを動的に取得する
AZの取得は以下のようになります。
Subnet1: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC AvailabilityZone: !Select [ 0, !GetAZs ] CidrBlock: !Select [ 0, !Cidr [ !GetAtt VPC.CidrBlock, 4, 8 ]] Subnet2: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC AvailabilityZone: !Select [ 1, !GetAZs ] CidrBlock: !Select [ 1, !Cidr [ !GetAtt VPC.CidrBlock, 4, 8 ]] Subnet3: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC AvailabilityZone: !Select [ 2, !GetAZs ] CidrBlock: !Select [ 2, !Cidr [ !GetAtt VPC.CidrBlock, 4, 8 ]]
強調表記したところになるのですが、CloudFormationの組み込み関数である !GetAZs
はリージョンのAZのリストを返す関数になります。
それを、!Select
で指定して取得することで、AZの情報を取得することが可能です。
Subnetごとに割り当てるCidrを計算させる。
これも組み込み関数の!Cidr
を用いることでVPCに割り当てたCIDRから計算させる事が可能です。
これについては以下のブログで非常にわかり易く解説されております。
構築してみた
以下のような形で構築が可能です。
$ sh ./create_Redshift_Serverless.sh check deviosample "/cm-kajiwara/redshift-serverless-admin-user-password" YourAWSCLIProfile Waiting for changeset to be created.. Changeset created successfully. Run the following command to review changes: aws cloudformation describe-change-set --change-set-name arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/awscli-cloudformation-package-deploy-1663432179/4f875331-1cd9-4a9b-86db-f98871abfca8
まとめ
Redshift Serverlessを構築するためのCloudFormationのテンプレートとシェルを公開してみました。 単一のCloudformationになっていますので、不要になったときの片付けも容易かと思います。 検証時には是非お使いください。