島根県産SEの備忘録

島根県産のSEがAWSなどのクラウドの技術を中心に記事を書いています。

CDK for Terraform で適当にEC2インスタンスをデプロイしてみた

環境準備

今回の環境は以下の通りです。

$ cat /etc/issue
Amazon Linux AMI release 2018.03

$ node --version
v12.18.2

$ npm --version
6.14.5

$ yarn version
yarn version v1.22.4

$ terraform version
Terraform v0.12.28

$ yarn -s cdktf --version
0.0.12

それでは、上述のサービスのインストール及び、アップグレードの方法から紹介していきます。

node.js / npm

まずはnode.jsとnpmです。
Cloud9のAMI使用していますが、デフォルト入っているnodeのバージョンが0.10だったので、アップグレードしていきます。

// リポジトリの追加
$ sudo su -
> curl -sL https://rpm.nodesource.com/setup_12.x | bash -

// node.jsのアップグレード
> yum update nodejs

// バージョン確認
> node --version
> nom --version

yarn

yarnはインストールされていなかったようなので、インストールしていきます。

// リポジトリの追加
> wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo

// yarnのインストール
>  yum install yarn

// バージョン確認
> yarn version

terraform

Terraformのインストールです。
上述の参考サイトにある通り、terraformがインストールされていない状態で、CDK for Terraform(cdktf-cli)をインストールしようとすると怒られます。

なお、Terraformがインストールされていない環境で実行すると # Terraform CLI not present - Please install a current version https://learn.hashicorp.com/terraform/getting-started/install.html とエラーが表示されます、事前にインストールしておいてください。

AWS CDKでプロバイダーとしてTerraformが使える!!CDK for Terraformが発表されました!! #awscdk | Developers.IO

インストール手順は以下の通りです。

// terraform用のディレクトリを用意
> mkdir /opt/terra_0_12_28/

// zipファイルのダウンロード
> wget https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip

// zipを先に作成したディレクトリに解凍
> unzip -d /opt/terra_0_12_28/ /home/ec2-user/environment/terraform_0.12.28_linux_amd64.zip

// 後ほど/opt/terraformにパスを通すので、シンボリックリンクを作成
> ln -sfn /opt/terra_0_12_28 /opt/terraform

// パスを通す
> vi /etc/profile

    (以下を追記)
    export PATH=$PATH:/opt/terraform/

// /etc/profile読み込み
> source /etc/profile 

// 一般ユーザーでもパスが通っていることを確認
> exit 
$ which terraform

// バージョン確認
$ terraform version

cdktf-cli

ここまで準備できたら、いよいよcdktf-cliのインストールです。
今回は参考サイトにならって、ローカルインストールにしようと思います。

// インストール先ディレクトリの作成
$ mkdir terraform-cdk && cd terraform-cdk

// cdktf-cliのインストール及び、cdktfの初期化(Typescript)
$ npx -p cdktf-cli cdktf init --template=typescript

EC2インスタンスのデプロイ

早速以下の通り、EC2インスタンスをデプロイするように、main.tsファイルを編集してみました。

main.ts

import { Construct } from 'constructs';
import { App, TerraformStack, Token } from 'cdktf';

import { AwsProvider } from './.gen/providers/aws';

import { Vpc } from './.gen/providers/aws/vpc';
import { Subnet } from './.gen/providers/aws/subnet';
import { RouteTable } from './.gen/providers/aws/route-table';
import { RouteTableAssociation } from './.gen/providers/aws/route-table-association';
import { InternetGateway } from './.gen/providers/aws/internet-gateway';

import { Instance } from './.gen/providers/aws/instance';


class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new AwsProvider(this, 'Aws', {
      region: 'us-east-1',

    })

    const vpc = new Vpc(this, 'Vpc', {
      cidrBlock: '10.0.0.0/16'
    });
    
    const igw = new InternetGateway(this, 'InternetGateway', {
      vpcId: Token.asString(vpc.id)
    });
    
    const rt = new RouteTable(this, 'RouteTable', {
      vpcId: Token.asString(vpc.id),
      route: [
        {
          cidrBlock: '0.0.0.0/0',
          gatewayId:  Token.asString(igw.id),
          egressOnlyGatewayId:  '',
          instanceId:  '',
          ipv6CidrBlock:  '',
          natGatewayId:  '',
          networkInterfaceId:  '',
          transitGatewayId:  '',
          vpcPeeringConnectionId:  ''
        }
      ]
    });
    
    const sbn = new Subnet(this, 'Subnet', {
      vpcId: Token.asString(vpc.id),
      availabilityZone: 'us-east-1a',
      cidrBlock: '10.0.0.0/24'
    });
    
    new RouteTableAssociation(this, 'RouteTableAssociation', {
      routeTableId: Token.asString(rt.id),
      subnetId: Token.asString(sbn.id)
    });
    
    new Instance(this, 'Instance', {
      ami: 'ami-08f3d892de259504d',
      instanceType: 't2.micro',
      subnetId: Token.asString(sbn.id)
    });
    
  }
}

const app = new App();
new MyStack(app, 'terraform-cdk');
app.synth();


ほとんど参考サイトの記載と同じですね。
少し違うところといえば、IGWや、ルートテーブルの作成をしているところですかね。


ルートテーブルのところについては、地味に詰まってしまいました。
IGWしかルーティングしないため、gatewayIdのみ書いていましたが、以下のようなエラーが出ました。

Inappropriate value for attribute "route": element 0: attributes
"egress_only_gateway_id", "instance_id", "ipv6_cidr_block", "nat_gateway_id",
"network_interface_id", "transit_gateway_id", and "vpc_peering_connection_id"
are required.

解決方法としては上述の通り、今回のルーティング設定には関係ないegressOnlyGatewayIdや、instanceIdなどについても書いてあげる必要がありました。
./.gen/providers/aws/route.ts を確認しても、required じゃないのに何故...


また、各種リソースに指定できるオプションは、./.gen/providers/aws/<リソース名>.tsexport interface <リソース名>Configを見れば確認できます。

e.g.) ./.gen/providers/aws/route.ts

export interface RouteConfig extends TerraformMetaArguments {
  readonly destinationCidrBlock?: string;
  readonly destinationIpv6CidrBlock?: string;
  readonly egressOnlyGatewayId?: string;
  readonly gatewayId?: string;
  readonly instanceId?: string;
  readonly natGatewayId?: string;
  readonly networkInterfaceId?: string;
  readonly routeTableId: string;
  readonly transitGatewayId?: string;
  readonly vpcPeeringConnectionId?: string;
  /** timeouts block */
  readonly timeouts?: RouteTimeouts;
}


順番が少し前後しましたが、デプロイなどの基本的な操作は以下の通りです。

cdktf synth: cdktf.outにcdk.tf.jsonというJSON形式のTerraformのテンプレートファイルを出力する。

e.g.)

$ yarn -s cdktf synth
Generated Terraform code in the output directory: cdktf.out


cdktf deploy: CDKで記載したリソースをTerraformテンプレートに変換してデプロイする。

e.g.)

$ yarn -s cdktf deploy
Deploying Stack: terraform-cdk
Resources
 ✔ AWS_INSTANCE         Instance            aws_instance.terraformcdk_Instance_D75D061F
 ✔ AWS_INTERNET_GATEWAY InternetGateway     aws_internet_gateway.terraformcdk_InternetGateway_12383DC6
 ✔ AWS_ROUTE_TABLE      RouteTable          aws_route_table.terraformcdk_RouteTable_FE93546D
 ✔ AWS_ROUTE_TABLE_ASSO RouteTableAssociati aws_route_table_association.terraformcdk_RouteTableAssociation_6D40008E
 ✔ AWS_SUBNET           Subnet              aws_subnet.terraformcdk_Subnet_D427F1FE
 ✔ AWS_VPC              Vpc                 aws_vpc.terraformcdk_Vpc_DF4745BC

Summary: 6 created, 0 updated, 0 destroyed.


cdktf synth: cdktf deployでデプロイしたリソースを削除する。

e.g.)

$ yarn -s cdktf destroy
Destroying Stack: terraform-cdk
Resources
 ✔ AWS_INSTANCE         Instance            aws_instance.terraformcdk_Instance_D75D061F
 ✔ AWS_INTERNET_GATEWAY InternetGateway     aws_internet_gateway.terraformcdk_InternetGateway_12383DC6
 ✔ AWS_ROUTE_TABLE      RouteTable          aws_route_table.terraformcdk_RouteTable_FE93546D
 ✔ AWS_ROUTE_TABLE_ASSO RouteTableAssociati aws_route_table_association.terraformcdk_RouteTableAssociation_6D40008E
 ✔ AWS_SUBNET           Subnet              aws_subnet.terraformcdk_Subnet_D427F1FE
 ✔ AWS_VPC              Vpc                 aws_vpc.terraformcdk_Vpc_DF4745BC

Summary: 6 destroyed.

まとめ・感想

今まではCDK はCloudFormationを介して操作できるものしか触れませんでしたが、Terraformにも対応したことで、クラウドのリソース管理をするならまずCDKを検討してから のように一気に可能性が広がったような気がします。

感想としては、私自身Terraformに触れるのは初めてでしたが、思ったよりも簡単に操作することができ、面白かったです。
これも、コードがTypescriptで記述されており、なんとなく見たことあるような構文が並んでいるからだと思います。

また、書いている途中ですが、Cloud9の補完がかなり効いててスムーズに編集することができました。素のTerraformのテンプレートファイルの.jsonや、.tf ファイルと比べるとそういった利便性の向上も期待できそうだと感じました。

AWS Cloud9 のターミナルの出力結果をCloudWatch Logsに保存する

皆さんはAWS Cloud9使っていますか?

私はボチボチです。

 

AWS Cloud9を使うと、チーム共通のIDE環境としての利用は勿論、IAMユーザーのアクセスキーを発行しなくとも、Cloud9のターミナルで、使用しているIAMユーザーの権限でAWS CLIが使えるので非常に便利です。

 

Cloud9での権限周りについての説明はclassmethodさんのブログに記載されています。

dev.classmethod.jp

 

ただ一点、Cloud9のターミナルのログを保存する機能がないことが少し不満です。

 

みんな大好きTerraformやiTerm2では、操作ログを指定したファイルに保存できますが、Cloud9ではこの機能はありません。

 

対策としてパッと私が考えた方法は以下3点です。

  1. 作業を終えたら、ターミナルを全選択 -> コピーしてファイルに保存する
  2. ログインしたら絶対 script コマンドを実行する
  3. psacctを自動起動するようにしておく

 

1番と2番はオペミス起きそうなので論外ですね...

特に1番は間違えてターミナル上で貼り付けしたら、クリップボードに保存されている内容が全て実行されてしまうので地獄です。

 

3番は一番シンプルで実装も簡単ですが、以下のようにコマンドのオプションや実行結果までは知ることはできません。

※ しかもbashだらけで良くわからない...

 

$ lastcomm --user ec2-user


bash F ec2-user pts/2 0.00 secs Sat Jul 18 04:19
bash F ec2-user pts/2 0.00 secs Sat Jul 18 04:19
git ec2-user pts/2 0.00 secs Sat Jul 18 04:19
bash F ec2-user pts/2 0.00 secs Sat Jul 18 04:19
bash F ec2-user pts/2 0.00 secs Sat Jul 18 04:19
bash F ec2-user pts/2 0.00 secs Sat Jul 18 04:19
git ec2-user pts/2 0.00 secs Sat Jul 18 04:19
bash F ec2-user pts/2 0.00 secs Sat Jul 18 04:19

 

そのため、私の圧倒的なググり力を武器に、以下の方法でターミナルのログを利用者の手間をかけずに保存するようにしました。

 

1. ~/.bash_prrofileを編集し、ターミナルを開いたら script コマンドを実行するように設定する

以下サイトを参考に ~/.bash_profileを編集します。

 

ssh上で行った作業のログをとる ー もぎゃろぐ

scriptとpsacctでオペレーションログを記録する | Developers.IO

 

~/.bash_profileの末尾には以下内容を追記します。

 

P_PROC=`ps aux | grep $PPID | grep bash | awk '{ print $11 }'`
if [ "$P_PROC" = "bash" ]; then
  script -q /var/log/script/`whoami`_`date '+%Y%m%d%H%M%S'`.log
  exit
fi

 

参考サイトと異なる箇所は、確認するプロセスの対象がsshd ではなく bash に変更しています。

これは参考サイトがサーバにSSHで接続する場面を想定しているのに対して、今回のCloud9のターミナルの場合はSSHを使用せず、直接bashを実行しているからです。

 

また、事前にscriptコマンドの出力ファイル保存先のディレクトリを作成するのも忘れずに。

保存先のディレクトリの権限は以下のように、実行権限が必要でした。

※ 理由は不明。誰か教えてください...

 

drwxr--r-- 2 ec2-user ec2-user 4096 Jul 18 04:17 /var/log/script/

 

動作確認として、Cloud9で別ターミナルを立ち上げ、適当に操作してからターミナルを閉じると、以下のようにディレクトリにファイルが保存されています。

 

$ ls -l /var/log/script/

-rw-rw-r-- 1 ec2-user ec2-user 644 Jul 17 13:51 ec2-user_20200717135050.log

 

2. scriptコマンドの出力ファイルをCloudWatch Logsに保存する

以下サイトを参考にscriptコマンドの出力ファイルをCloudWatch Logsに保存します。

 

AmazonLinux2のシスログをCloudWatch Logs に転送させてみた | Developers.IO

 

まずは、Cloud9が動いているEC2インスタンスがCloudWatch LogsにアクセスできるようにIAMロールをアタッチします。

 

今回は、CloudWatchlogsFullAccess のIAMポリシーを付与したIAMロールをEC2インスタンスにアタッチしました。

 

続いて、以下のようにClouwatch Logsのインストール及び、起動、自動起動の設定です。

※ Cloud9のAMIはAmazonLinuxベースだったので、systemctlコマンドではなく、serviceコマンドや chkconfigコマンドを使っています。

 

// インストール

$ sudo yum insyall -y awslogs

 

// サービス起動

$ sudo service awslogs start

 

// サービス起動確認

$ sudo service awslogs status

 

// サービス自動起動設定

$ sudo chkconfig awslogs on

 

// サービス自動起動設定確認

$ sudo chkconfig --list awslogs

 

そして、CloudWatch Logsの設定です。

/etc/awslogs/awslogs.conf の末尾に以下の内容を追記します。

 

[general]
state_file = /var/lib/awslogs/agent-state

 

[/var/log/script/*.log]
datetime_format = %b %d %H:%M:%S
file = /var/log/script/*.log
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = cloud9-syslog-/var/log/script/.log 

 

最後に設定を反映させるために、 CloudWatch Logsのサービスを再起動して、設定は完了です。

 

sudo service awslogs restart

 

以下コマンドでCloudWatch Logsを確認してみると、正常に転送できていることが確認できると思います。

 

$ aws logs get-log-events \ 

   --log-group-name cloud9-syslog-/var/log/script/.log \

   --log-stream-name <Cloud9が動作しているEC2インスタンスのID> \

    --query "events[].[message]" \

    --output text > script.log

 

$ less script.log

 

初めてのブログからして、なんだか長いですね...

次はもう少し文章まとめられるように頑張ります。