Zenn

AWS CDK実行環境をCloudFormationで用意する

こんにちは、エンジニアの籏野です。

フォルシアでは主にAWS上に用意した環境にアプリをデプロイしており、初期のころはCloudFormationのテンプレートを書いて環境を構築していました。
近年ではAWS CDKを利用することが多くなってきており、単なるYAML等の設定ファイルではなく自分たちに馴染みのある言語で環境を記載できてとても便利に感じています。
AWS CDKを利用しているので、最終的にはcdk deployコマンドを実行する必要がありますが、このコマンドをどこで実行するのかというのは重要なポイントです。
AWSのアクセスキーを発行することで、ローカルの開発環境や社内に用意するデプロイ専用環境等からの実行も可能ですが、安全に運用するためにはアクセスキーをなるべく発行しないようにしたいです。

最近はGitHubやGitLabなどでOIDC連携をして実行するのが定石になってきているような気もしますが、学習の意も込めてAWS CDK実行用のEC2を用意することにしました。

https://zenn.dev/alivelimb/articles/20220503-aws-accesskey-sec

上記の記事も参考にさせていただき、AWS CDK実行用のEC2にIAMロールを付与していきます。
また今回作成する環境の設定もコードに残しておきたいので、CloudFormationで設定を記載し構築していきます。

構築すべき環境

CloudFormation設定ファイルを書く前に、どのような環境を構築するかを整理します。

  • EC2はパブリックサブネットに配置
  • 特定のIPからのSSH接続のみ許可
  • AWS CDK実行用のIAMロールを作成し、最低限の権限を付与
  • TypeScript版のAWS CDKを利用しているため、Node.jsをインストール

上3つについてはよく構築する構成なので特に問題なかったのですが、Node.jsのインストールについてはどのような方法で行うのかがわかりませんでした。
フォルシアではサーバーの構築にはAnsibleを主に利用してきており、これを使えばNode.jsのインストールも容易ですが、playbookを別で用意するのも面倒です。
そのためEC2のユーザーデータという機能を利用して、起動時にNode.jsをインストールするように設定します。

CloudFormation設定

早速結論です。

# NOTE 複数のアプリでの利用を想定し、パラメータを設定
Parameters:
  BaseName:
    Type: String
    Default: cdk-deploy
    Description: The base name of the resources
  VpcCidr:
    Type: String
    Default: 10.0.0.0/24
    Description: The CIDR block for the VPC
  PublicSubnetCidr:
    Type: String
    Default: 10.0.0.0/24
    Description: The CIDR block for the public subnet
  PublicSubnetAZ:
    Type: String
    Default: ap-northeast-1a
    Description: The availability zone for the public subnet

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${BaseName}-VPC

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${BaseName}-IGW

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Ref PublicSubnetAZ
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnetCidr
      Tags:
        - Key: Name
          Value: !Sub ${BaseName}-PublicSubnet

  PublicSubnetRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${BaseName}-PublicSubnet-RT

  PublicSubnetToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnetRT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetRTAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicSubnetRT

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable Access
      GroupName: !Sub ${BaseName}-SG
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
	  # NOTE 接続元IPを記載
          CidrIp: XXX.XXX.XXX.XXX/XX
      Tags:
        - Key: Name
          Value: !Sub ${BaseName}-SG
      VpcId: !Ref VPC

  CDKRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Principal: 
              Service: 
                - "ec2.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"

  CDKBoorstrapPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub ${BaseName}-bootstrap-policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cloudformation:*
              - ecr:*
              - ssm:*
              - s3:*
              - iam:*
            Resource: "*"
      Roles:
        - !Ref CDKRole

  CDKDeployPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub ${BaseName}-deploy-policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: "AssumeCDKRoles"
            Effect: Allow
            Action:
              - sts:AssumeRole
            Resource: "*"
            Condition:
              "ForAnyValue:StringEquals":
                "iam:ResourceTag/aws-cdk:bootstrap-role":
                  - image-publishing
                  - file-publishing
                  - deploy
                  - lookup
      Roles:
        - !Ref CDKRole

  CDKIamInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
        - !Ref CDKRole

  EC2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub ${BaseName}-launch-template
      LaunchTemplateData:
        UserData:
          Fn::Base64: |
              #!/bin/bash -xe 
              # Install nvm to ec2-user
              runuser -l ec2-user -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      # Amazon Linux 2023
      ImageId: ami-034c9ca2bdde7b472
      # NOTE 接続用のキーペアを指定
      KeyName: hoge-key 
      InstanceType: t3.small
      IamInstanceProfile: !Ref CDKIamInstanceProfile
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref SecurityGroup
      Tags:
        - Key: Name
          Value: !Sub ${BaseName}
      LaunchTemplate:
        LaunchTemplateId: !Ref EC2LaunchTemplate
        Version: !GetAtt EC2LaunchTemplate.LatestVersionNumber
  EC2ElasitcIP:
    Type: AWS::EC2::EIP
  EC2ElasticIPAssociation:
    Type: AWS::EC2::EIPAssociation
    Properties:
      InstanceId: !Ref EC2Instance
      EIP: !Ref EC2ElasitcIP

VPC、サブネット、セキュリティグループ、IAMロール、EC2インスタンスを作成しました。
また以下のような設定により作成するEC2インスタンスにユーザーデータを設定できます。
これによりインスタンス起動時にNode.js(nvm)のインストールを行うことができます。
runuserコマンドを利用することで、特定のユーザー権限でコマンドを実行できます。
※今回は簡単のため、ec2-userにnvmをインストールしていますが、要件に合わせて実行用ユーザーを作成するなどの対応をしましょう。

  EC2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub ${BaseName}-launch-template
      LaunchTemplateData:
        UserData:
          Fn::Base64: |
              #!/bin/bash -xe 
              # Install nvm to ec2-user
              runuser -l ec2-user -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'

また、付与したポリシーについては以下の記事を参考とさせていただきました。
https://dev.classmethod.jp/articles/cdk-minimum-deploy-policy/

上記の設定を元にCloudFormationスタックを作成し、AWS CDKの実行環境を構築することができました。

最後に

これまでは構築したサーバーに対して何かしらのインストールを行うのは、CloudFormationとは別の仕組みを用いてきました。
しかし、今回のような簡単な構築であればユーザーデータを用いることでCloudFormationで完結できることがわかりました。
より大規模な構築についてはAnsible等を利用した方がよいように思いますが、要件に合わせて柔軟に使い分けていけるとよいですね。

この記事を書いた人

籏野 拓
2018年新卒入社

FORCIA Tech Blog

Discussion

ログインするとコメントできます