目的
AWSで仮想マシンやオブジェクトストレージ、仮想マシンを置く仮想ネットワーク環境をまるっと作る.
マネジメントコンソールでもできるが、結構手間なのと、同じ環境を再現するのが大変なので、
環境構築をCloudFormationでスクリプト化する
構築したい環境
- 仮想ネットワークとしてVPCと一つのパブリックサブネットを用意
- 仮想ネットワーク環境の中にEC2を一つ建てる.
- オブジェクトストレージ(S3)のバケットを作成
- EC2には作成したバケットのみにアクセスできる権限(policy)を付与
- 起動するEC2はubuntu20.04イメージを使用し、awscliを起動時にインストール
セキュリティ
- EC2へはsshのみ接続のみ許可
- ssh接続時にkeypairを要求
CloudFrmation
AWSでIaCを実現するツールの一つで、起動したいAWSサービスをまとめてjsonやymlに記述し、AWSで実行させることでまとめてサービスを起動する. CloudFormationで起動される一連のサービス群をstackと呼ぶ
メリットは
- 同じAWS環境を再現できる
- タグが必ず振られるので、コスト管理の際にstackごとのコストが確認できる
- 起動したサービスはまとめて削除できる
など。
AWSでIaCをやりたい場合、Terraformという選択肢もある. が、AWSネイティブのサービスであるCloudFormationを使用.
事前準備
以下は事前に実施しておく必要がある
- AWSのアカウント作成
- IAMユーザーの作成
ymlの用意
作成したいAWSサービス群(stack)を記載したファイルを作成. jsonでも良いが、ymlのようがコメントかけたりして便利なのでymlを使用.
cloudformationのymlは基本的に以下の構成
Parameters: # ここに設定値などを記載しておく
Resources: # ここに作成/起動したいサービスとその条件を記載
Outputs: # 実行結果の出力
以下に今回作成したymlを格納している
cloudformation yml sample (github)
yml記載内容説明
Parameter設定
パラメータ部分は以下のように設定. パラメータの値はAWSでstackを起動する際に使用.
Parameters:
PJPrefix:
Type: String
VPCCIDR:
Type: String
Default: "10.1.0.0/16"
PublicSubnetACIDR:
Type: String
Default: "10.1.10.0/24"
Resources
Resources以下に起動するサービスの条件を設定していく.
ネットワークの設定
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${PJPrefix}-vpc"
InternetGateway:
Type: "AWS::EC2::InternetGateway"
InternetGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicSubnetA:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "us-east-1a"
CidrBlock: !Ref PublicSubnetACIDR
VpcId: !Ref VPC
PublicRouteTableA:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
PublicRouteA:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTableA
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
PublicSubnetARouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref PublicRouteTableA
構成要素はVPC, Subnet, RouteTable, InternetGatway.
InternetGatewayはVPCから外部インターネットと通信する際に必要. subnetは今回public subnetを一つだけ作成した.
subnetのAvailabilityZone: "us-east-1a"
ではsubnetのazを指定している。azはリージョンごとに指定できる値が異なる。
CloudFormationはリージョンごとのサービスなので、適宜リージョンを変えて実行.
このymlはバージニア北部リージョンで実行できる.
EC2のセキュリティ周り
ssh key pairとセキュリティグループを以下のように作成
DemoKeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: demo-key-pair
EC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "${PJPrefix}-sg"
GroupDescription: Allow SSH access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
ssh private keyの情報はAWS system managerのパラメータストアに保存される(後述)
セキュリティーグループはここではsshのみを許可してて、接続元ipは0.0.0.0/0
(anywhere)としていて、これはセキュリティ的に少々不安なので、CidrIp:の部分を自分のipに設定し直した方が良いかもしれない
IAM Role
EC2にアタッチするIAMロールを設定
S3AccessRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
Tags:
- Key: Name
Value: !Sub "iamrole-${PJPrefix}"
S3AccessPolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: s3access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "s3:ListAllMyBuckets"
- "s3:GetBucketLocation"
Resource: "arn:aws:s3:::*"
- Effect: Allow
Action: "*"
Resource:
- !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}"
- !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}/*"
Roles:
- !Ref S3AccessRole
S3AccessInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: "/"
Roles:
- !Ref S3AccessRole
ここでは
Resource:
- !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}"
- !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}/*"
に対して全ての動作を許可している.
EC2起動
EC2を以下のような設定で起動
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-06aa3f7caf3a30282
InstanceType: t2.micro
KeyName: !Ref DemoKeyPair
SecurityGroups:
- !Ref EC2SG
IamInstanceProfile:
!Ref S3AccessInstanceProfile
Tags:
- Key: Name
Value: !Sub "ec2-${PJPrefix}"
UserData:
Fn::Base64: |
#!/bin/bash
sudo apt update
sudo apt upgrade
sudo apt install awscli -y
InstanceType: t2.micro
でインスタンスタイプを設定.
ImageId: ami-06aa3f7caf3a30282
でimage(osなどベースになるソフトウェア)を指定していて、
今回はubuntu20.04が入ったAMIを指定してる.
UserData:
Fn::Base64: |
に続けて起動時に実行したいコマンドを記載できる.今回はaws cliをインストールしている.
S3
S3Bucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub "s3-bucket-${PJPrefix}"
デフォルトの設定だとpublic accessはblockされている.今回はそのまま。
CloudFormation実行
AWS マネジメントコンソールにいき、サービス一覧からAWS CloudFormationを選択.
スタックの作成
という場所をクリックする
テンプレートの準備完了、テンプレートファイルのアップロード、を選択して先ほど作成したyml(githubのurl貼ったやつ)をuploadする.
ここでマネジメントコンソール右上のリージョンが想定通りか確認しておく.
uploadしたら次へを選択.
ここでは
- スタック名
- PJPrefix
- VPCCIDR
- PublicSubnetACIDR
を指定する. CIDRについてはデフォルト値のままでよければそのままでよい.
次の画面はそのまま次へと押せばよく、そうすると最終画面になる.
一番下の送信を押すと実行されるが、その前に
「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」という
確認事項が出てくるのでチェックする
(これはEC2にアタッチするIAM RoleをCloudFormationで作成するため)
送信を押すと、プロセス実行中の画面になるのでしばらく待つと起動が完了する.
右上のリロードマークを押すと進捗が更新される.
命名したスタック名のプロセスが完了したらOK.
何かエラーになったプロセスがある場合は「根本原因を抽出」でどのプロセスがエラーの大元かわかる.
成功した画面はこんな感じ
作成した環境に接続
Private Keyの取得
サービス一覧からSystem Managerを選択し、左側のパラメータストアを選択
パラメータストアには先ほどcloudformationで作成したキーが格納されている
キーを選択し、「値」の復号化された値を表示を押すと、private keyのテキストが表示される.
グレーで隠しているが、左下の部分にprivate keyの値が格納されているので、
local pcの適当なpathに key-for-ec2.pem
を作成して表示された中身を貼り付ける
-----BEGIN RSA PRIVATE KEY-----
表示された中身のの文字列
-----END RSA PRIVATE KEY-----
ここで注意なのは、そのままペーストすると、-----BEGIN RSA PRIVATE KEY-----
と
-----END RSA PRIVATE KEY-----
が改行されないので、ちゃんと改行する.
EC2へ接続
EC2>Instanceで作成されたインスタンスを選択する. 選択したらpublic ipをコピーする.
chmod 400 <path to key-for-ec2.pem>
ssh -i <path to key-for-ec2.pem> ubuntu@<public ip for ec2>
には先ほど作成したkeyのパスをにはインスタンスのpubic ipをコピーする.
接続すると、
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
と聞かれるのでyesとタイプしエンター.
そうすると接続できているはず。
また、作成したbucketをaws s3 lsでみれていることを確認.
課題事項
stackで作成したkey pairをいちいちコピーするのが面倒.
downloadはできないっぽい. EC2 Instance Connectとかの方が良いのかも
あとこれstackが巨大になると大変そうだから、cloud formationをpythonで叩けるAWS CDKも試してみたい