[Terraform+AWS]Pythonツールのインフラ(AWS)をTerraformで構築した話

はじめに

IaCとして注目されているTerraformについて、今更ながらインフラエンジニアとして興味があって触ってみようと言うのが動機です。

以前作成したツールのインフラ部分をAWSのGUIから作成しており、Terraformで構築し直してみるというのが目標です。

以前作成したツールは、Python+AWSで自動で定期的にHPの情報を取得して、更新があれば更新分の情報をツイートするというものです。(ツールについての詳細はこちらの記事へ)

実装した構成

AWS構成図

赤枠の箇所(+IAM)が今回Terraformで実装する部分です。

EventBridge:定期実行のトリガーを発出する。(現状1日2回としている。)

Lambda:EventBridgeのトリガーを受けてLambda実行する。
HPからのスクレイピング、取得した情報をcsv化してS3に格納、S3に格納されているCSVの読み込み/比較、TwitterAPIからの更新分のツイート等を行う。(Pythonコードについては本記事では割愛)

CloudWatch:Lambdaの実行ログを管理する。

S3:スクレイピングした情報をcsv化して格納する。

IAM(上記図にはなし):LambdaからAWSサービスへアクセスする際のロールを管理する。

環境

PC:Windows10 Home(Core i5-8400)
※WSL2上にLinux(Ubuntu)を入れております。(PCの環境構築はこちらの記事で説明しています。)

インフラ構築環境:AWS

Terraformとは

TerraformはIaC(Infrastracture as Code)を実現するOSSです。
言語としてHCL(Hashicorp Configuration Language)を使用して、インフラに関する情報を定義し構築、サービスの操作ができます。

その他のIaCであるAnsibleとの違いを簡単に言うと、Terraformはサービス間で値のやり取りをしてインフラ全体を構築することが得意、Ansibleはサーバにパッケージをインストールしたりサーバの中の操作が得意というイメージで良いと思います。

ソースコード(Terraform(HCL))

実際の実装内容やソースコードは以下に公開しております。
GitHub

フォルダ構成

・
├──main.tf ⇒メインファイル
├──terraform.tfvars ⇒メインファイルで使用する変数定義(アクセスキー等を記述するためGit等で公開する際には注意)
├──modules
| ├──iam
| | └──iam.tf ⇒IAMの実装
| ├──lambda
| | └──lambda.tf ⇒Lambdaの実装
| | └──variable.tf ⇒lambda.tfで使用する変数定義
| └──eventbridge
|   └──eventbridge.tf ⇒EventBridgeの実装
|   └──variable.tf ⇒variable.tfで使用する変数定義
| └──s3
|   └──s3.tf ⇒S3の実装
├──app
| └──lambda_function.py ⇒Lambdaで実行するPythonコード
└──archive
  └──lambda.zip ⇒Lambdaで実行するzipファイル(appディレクトリの中をzip化したもの)

メインファイル

Terraform実行時のいわゆるメインファイルです。
terraform.tfvarsではメインファイルで使用する変数を定義しています。

[provider aws]でAWSのプロバイダ情報を入れて、AWSに対してリソースの操作が出来るようにしています。
[module]で各モジュールの呼び出し、モジュールへ値を渡したりしています。

#awsアクセスキー
variable aws_access_key_id {}
variable aws_secret_access_key {}
#Lambda環境変数(twitterAPIキー)
variable twitter_access_token_key {}
variable twitter_access_token_key_secret {}
variable twitter_consumer_key {}
variable twitter_consumer_key_secret {}

#Terraformバージョン
terraform {
    required_version = "~> 0.12.5"
}

#プロバイダ情報
provider aws {
    version = "3.37.0"
    access_key = var.aws_access_key_id
    secret_key = var.aws_secret_access_key
    region = "ap-northeast-1"   
}
provider archive {
    version = "2.1.0"
}

#IAM
module iam {
    source = "./modules/iam"
}

#Lambda
module lambda {
    source = "./modules/lambda"
    role_arn = module.iam.lambda_role_arn
    policy = module.iam.lambda_policy
    twi_access_token_key = var.twitter_access_token_key
    twi_access_token_key_secret = var.twitter_access_token_key_secret
    twi_consumer_key = var.twitter_consumer_key
    twi_consumer_key_secret = var.twitter_consumer_key_secret
}

#Eventbridge
module cloudwatch_event {
    source = "./modules/eventbridge"
    lambda_arn = module.lambda.arn
    lambda_function_name = module.lambda.function_name
}

#S3
module s3 {
    source = "./modules/s3"
}
aws_access_key_id = "(AWSのアクセスキー)"
aws_secret_access_key = "(AWSのシークレットアクセスキー)"
twitter_access_token_key = "(TwitterAPIのアクセストークンキー)"
twitter_access_token_key_secret = "(TwitterAPIのシークレットアクセストークンキー)"
twitter_consumer_key = "(TwitterAPIのコンシューマキー)"
twitter_consumer_key_secret = "(TwitterAPIのシークレットコンシューマキー)"

IAM

IAMでLambdaに与えるロールを準備します。

Lambda用のロールを作成して、そのロールにLambdaの基本ポリシーとS3へのアクセスポリシーをアタッチします。
準備したロールをLambdaに付与するために、アウトプットとして出力しています。

#IAM
#信頼ポリシー(lambdaがこのロールを受け取れるようにする)
data aws_iam_policy_document assume_role {
    statement {
        actions = ["sts:AssumeRole"]
        effect  = "Allow"
        principals {
            type = "Service"
            identifiers = ["lambda.amazonaws.com"]
        }
    }
}

#IAMロールの作成
resource aws_iam_role lambda_role {
    name = "MyLambdaRole"
    assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

#IAMポリシー(AWSLambdaBasicExecutionRole)
data aws_iam_policy lambda_basic_execution {
    arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data aws_iam_policy s3_full_access {
    arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}

#IAMロールへのIAMポリシーのアタッチ
resource aws_iam_role_policy_attachment lambda_basic_execution {
    role = aws_iam_role.lambda_role.name
    policy_arn = data.aws_iam_policy.lambda_basic_execution.arn
}
resource aws_iam_role_policy_attachment s3_access {
    role = aws_iam_role.lambda_role.name
    policy_arn = data.aws_iam_policy.s3_full_access.arn
}

#Lambdaで使用する値のアウトプット
output lambda_role_arn {
    value = aws_iam_role.lambda_role.arn
}
output lambda_policy {
    value = aws_iam_role_policy_attachment.s3_access
}

Lambda

Lambdaの作成、LambdaへのIAMロールの付与、CloudWatchLogのグループ作成(3行だったためモジュールは分けずに作成しています)を行います。
variable.tfではlambda.tfで使用する変数を定義しています。(変数の中身はmain.tfやiam.tfから取得するため、空で定義しています。)

[archive_file function_source]でLambdaで実行するためのzipファイルの作成をしています。
次にLambdaの作成をしています。実行するツールはtwitterAPIを利用するため、[enviroment]でLambdaの環境変数を入れてます。

[aws_cloudwatch_log_group lambda_log_group]でCloudWatchLogsのグループを作成しています。
最後に、EventBridgeからLambdaへアクセスするために、Lambdaの情報をアウトプットとして出力しています。

#Lambda
#定数定義
locals {
    function_name  = "terraform_bangdre_function"
}

#lambda実行ファイル定義
data archive_file function_source {
    type = "zip"
    source_dir = "app"
    output_path = "archive/lambda.zip"
}

#lambda作成
resource aws_lambda_function function {
    function_name = local.function_name
    handler = "lambda_function.lambda_handler"
    runtime = "python3.8"
    timeout = 15
    filename = data.archive_file.function_source.output_path
    source_code_hash = data.archive_file.function_source.output_base64sha256
    role = var.role_arn
    depends_on = [var.policy, aws_cloudwatch_log_group.lambda_log_group]
    environment {
        variables = {
            ACCESS_TOKEN_KEY = "${var.twi_access_token_key}"
            ACCESS_TOKEN_KEY_SECRET = "${var.twi_access_token_key_secret}"
            CONSUMER_KEY = "${var.twi_consumer_key}"
            CONSUMER_KEY_SECRET = "${var.twi_consumer_key_secret}"
            TZ = "Asia/Tokyo"
        }
    }
}

#CloudWatchLogsグループ定義
resource aws_cloudwatch_log_group lambda_log_group {
    name = "/aws/lambda/${local.function_name}"
}

#EventBridgeで使用する値のアウトプット
output arn {
    value = aws_lambda_function.function.arn
}
output function_name {
    value = aws_lambda_function.function.function_name
}
variable role_arn {}
variable policy {}
variable twi_access_token_key {}
variable twi_access_token_key_secret {}
variable twi_consumer_key {}
variable twi_consumer_key_secret {}

EventBridge(旧CloudWatchEvents)

EventBridge(旧CloudWatchEvents)のルール作成、Lambdaへのアクセス権限の付与、Lambdaとの結び付けを行います。

variable.tfではeventbridge.tfで使用する変数を定義しています。(変数の中身はlambda.tfから取得するため、空で定義しています。)

#EventBridge
#ルール作成
resource aws_cloudwatch_event_rule event_rule {
    name = "cron-lambda-terraform"
    schedule_expression = "cron(0 11,15 * * ? *)"
}

#Lambdaアクセス権限付与
resource aws_cloudwatch_event_target event_target {
    rule = aws_cloudwatch_event_rule.event_rule.name
    arn  = var.lambda_arn
}

#EventbridgeとLambda関数への結び付け
resource aws_lambda_permission allow_cloudwatch {
    statement_id  = "AllowExecutionFromCloudWatch"
    action = "lambda:InvokeFunction"
    function_name = var.lambda_function_name
    principal = "events.amazonaws.com"
    source_arn = aws_cloudwatch_event_rule.event_rule.arn
}
variable lambda_arn {}
variable lambda_function_name {}

S3

S3のバケット作成を行います。
アクセスはプライベート、バージョニング機能はオフにしています。

#S3
#バケット作成
resource aws_s3_bucket bucket {
    bucket = "bangdream-eventlist-terraform"
    acl    = "private"
    versioning {
        enabled = false
    }
}

プログラム実行(Terraform)

上記のファイル等が入ったフォルダにて、以下の順番で実行します。

#作業フォルダの初期化、必要なproviderやmoduleの読み込み
terraform init
#実行した際にどうなるかの検証
terraform plan
#実行
terraform apply

つまずいたポイント

あまりないですが強いて言うなら、参考書等が少なくWebでの情報がAnsibleと比べると少なかったです。
Terraformの公式ページでパラメータの説明がされていて、そちらをメインに参考にしていました。

あとは以下の参考書もわかりやすくて便利でした。

参考にしたサイト

Terraform公式ページ
⇒Terraformの記述方法で一番参考にしました。パラメータごとの説明もされています。

Terraformで、AWS Lambda関数を登録して動かしてみる
TerraformでLambda(Cron起動)を構築する
⇒各サービス実装時に参考にさせていただきました。

Terraform – 仕組みと導入部分を簡単にまとめてみる
⇒Terraform自体の仕組みが細かく書かれています。

最後に

今回は「[Terraform+AWS]Pythonツールのインフラ(AWS)をTerraformで構築した話」を紹介しましたが、いかがだったでしょうか?

本ブログでは、ゲーム開発や心理学、IT全般、資格取得の最短勉強法についての情報を発信しています。
今後もコンテンツを追加していく予定なので、他にも気になる記事があればぜひご覧いただけると嬉しいです。

記事に対する感想や記事のリクエストについては、お問い合わせからいただけると幸いです。