こしあん
2024-12-14

AWSのCodePipelineでCI/CDを試す


41{icon} {views}


GitHubのメインブランチへのプッシュをトリガーとして、Terraformを使用してS3バケットにソースコードを自動的にコピーするAWS CodePipelineを構築しました。Code ConnectionsとCodeStar Connectionsの違いに伴うハマりどころや、GitHubとの連携設定の手順、さらに個人での利用を考慮したCodePipelineとGitHub Actionsの比較について解説します。

はじめに

  • DVAの勉強してたら、AWSのCI/CDができるCodePipelineがやたら出てきたので触ってみた。最初の連携のところが結構ハマる部分が多かった。
  • 作るものは、GitHubへのメインブランチへのPushをトリガーとしてS3バケットにソースコードをコピーするだけのもの。
  • AWSのソース管理サービスにCode Commitがあるが、2024/12現在新規受付を停止しているので、GitHubとの連携で考える
  • GitHubの連携は、Code Connectionsで行い、GitHub上にアプリをデプロイする

注意点

  • Code Connectionsと似たものにCodeStar Connectionsがあるが、こちらではGitHubでは現在連携できないので注意。Terraformのリソース名が異なる。
    • 正:aws_codeconnections_connection
    • 誤:aws_codestarconnections_connection
  • GPTだと古いもの(CodeStar)を出してきてかなりハマったので、要注意

事前準備

GitHub非公開リポジトリを作る。sample-private-repositoryとして、デフォルトブランチはmasterとしてみた。

Terraformのコード

Code PipelineやCode ConnectionsはTerraformでサクッとデプロイする。

※IAMポリシーはガバガバなので、頑張りたければ絞って

variable.tf

# 変数定義
variable "github_owner" {
  description = "GitHubのオーナー名または組織名"
  type        = string
  default     = "koshian2"
}

variable "github_repo" {
  description = "GitHubリポジトリ名"
  type        = string
  default     = "sample-private-repository"
}

variable "github_branch" {
  description = "デプロイに使用するGitHubのブランチ"
  type        = string
  default     = "master"
}

variable "s3_bucket_name" {
  description = "デプロイ先のS3バケット名"
  type        = string
  default     = "codepipeline-deploy-destination-bucket"
}

variable "connection_name" {
  description = "Code Connectionsの名前"
  type        = string
  default     = "MyGitHubConnection"
}

main.tf

これでvar.github_repovar.github_branchにPushが入ったときに、指定したバケットに自動的に転送されるようになるが、GitHubとの連携にマネジメントコンソールから初期設定が必要。それを次以降に説明する。

# main.tf
# S3バケットの作成
resource "aws_s3_bucket" "deploy_bucket" {
  bucket        = var.s3_bucket_name
  force_destroy = true
}

# Code Connectionsの作成
resource "aws_codeconnections_connection" "github_connection" {
  name          = var.connection_name
  provider_type = "GitHub"
}

resource "aws_s3_bucket_versioning" "versioning_deploy" {
  bucket = aws_s3_bucket.deploy_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

# IAMロールの作成
resource "aws_iam_role" "codepipeline_role" {
  name = "codepipeline_service_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect = "Allow",
      Principal = {
        Service = "codepipeline.amazonaws.com"
      },
      Action = "sts:AssumeRole"
    }]
  })
}

# IAMポリシーの作成
resource "aws_iam_policy" "codepipeline_policy" {
  name        = "codepipeline_policy"
  description = "Policy for CodePipeline to access S3 and CodeStar Connections"

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      # S3へのアクセス許可
      {
        Effect = "Allow",
        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:ListBucket"
        ],
        Resource = [
          aws_s3_bucket.deploy_bucket.arn,
          "${aws_s3_bucket.deploy_bucket.arn}/*"
        ]
      },
      # Code Connectionsへのアクセス許可
      {
        Effect = "Allow",
        Action = [
          "codestar-connections:UseConnection"
        ],
        Resource = aws_codeconnections_connection.github_connection.arn
      },
      # CodePipelineがAWSサービスにアクセスするための基本的な権限
      {
        Effect = "Allow",
        Action = [
          "codebuild:BatchGetBuilds",
          "codebuild:StartBuild",
          "codestar-connections:UseConnection"
        ],
        Resource = "*"
      }
    ]
  })
}

# IAMロールにポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "codepipeline_policy_attachment" {
  role       = aws_iam_role.codepipeline_role.name
  policy_arn = aws_iam_policy.codepipeline_policy.arn
}

# CodePipelineの作成
resource "aws_codepipeline" "pipeline" {
  name          = "MySimplePipeline"
  role_arn      = aws_iam_role.codepipeline_role.arn
  pipeline_type = "V2"

  artifact_store {
    type     = "S3"
    location = aws_s3_bucket.deploy_bucket.bucket

    encryption_key {
      type = "KMS"
      # デフォルトのS3管理キーを使用
      id = "alias/aws/s3"
    }
  }

  stage {
    name = "Source"

    action {
      name             = "GitHub_Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["source_output"]

      configuration = {
        ConnectionArn    = aws_codeconnections_connection.github_connection.arn
        FullRepositoryId = "${var.github_owner}/${var.github_repo}"
        BranchName       = var.github_branch
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      name            = "S3_Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "S3"
      input_artifacts = ["source_output"]
      version         = "1"

      configuration = {
        BucketName = aws_s3_bucket.deploy_bucket.bucket
        Extract    = "true"
      }
    }
  }
}

CodePipelineのマネジメントコンソールの設定

初期状態では失敗している

マネジメントコンソールからCodePipeline→パイプラインを見ると、「MySimplePipeline」が失敗しているのがわかる。これはGitHubに接続できていないため。

GitHubに接続する

CodeConnectionsはどこにあるのかというと、CodePipelineの下の「設定」→「接続」部分。ここに「MyGitHubConnection」が保留中になっている。

このあとポップアップが出てくるのでいくつか許可していく。

GitHub側からの確認

「Installed GitHub Apps」に「AWS Connector for GitHub」があれば成功。前のバージョンのCodeStar Connectionsで導入すると、その横の「Authorized Github Apps」にアプリができているが、そちらではこのパイプラインは正常に動作しない。

再度パイプラインを実行

CodePipelineにも戻り「変更をリリースする」などをクリック

今度はパイプラインの実行に成功している。

S3バケットの確認

S3バケットを見ると、ソースコードが同期されているのがわかる。

元のリポジトリに変更を加えてみる

GitHubのブラウザから「test.md」を作り、メインブランチに直でマージする。

するとすぐにCodePipelineが走り、S3バケットに反映されている。バージョニング入れたときのコストは気になるが、ある程度デカくなったらS3のライフサイクルルールを入れろということなのだろう。

CodePipelineの使い所

GitHub Actionsとの比較

CodePipelineの料金はAWSの公式ページより以下の通り

V1 タイプのパイプラインの場合: アクティブなパイプライン(30 日以上存在していて、その月に少なくとも 1 つのコード変更が発生したパイプライン) ごとに1か月あたり 1.00 USD を支払います。その月に新しいコード変更がないパイプラインに対しては、料金は発生しません。アクティブなパイプラインは、1 か月に満たない分に対して按分計算されません。パイプラインは、作成後の最初の 30 日間は無料です。

V2 タイプのパイプラインの場合: アクション実行時間 1 分あたり 0.002 USD を支払います。アクション実行時間は、パイプライン内のアクションが実行開始してからそのアクションが完了状態に達するまで分単位で計算され、最も近い分単位に切り上げられます。手動承認およびカスタムアクションタイプを除くすべてのアクションタイプに対して課金されます。

V1 タイプのパイプラインの場合: 1 か月あたり 1 つの無料のアクティブなパイプライン。
V2 タイプのパイプラインの場合: 1 か月あたり 100 分の無料のアクション実行。無料のアクション実行時間は、アカウント内の V2 パイプラインの数に関係なく、アカウント内のすべての V2 パイプライン間で共有されます。これらは各暦月の初めに自動的にリセットされます。未使用の分が翌月に繰り越されることはありません。

1ヶ月に500分以上CI/CDが走ればV1のほうが安いが、普通に考えるとV2一択だと思われる。上記のTerraformはV2で書いているが、TerraformのデフォルトだとV1でデプロイされてしまうので要注意

一方でGitHub Actionsの無料枠は以下の通り。

プラン Storage 分 (月あたり)
GitHub Free 500 MB 2,000

普通に考えるとGitHub Actionsのほうが無料枠は大きいので、特段大きなストレージが必要とかでなければActionsでいいように思える。

セキュリティの観点から

GitHub Actionsとの大きな違いは、CodePipelineはAWSの閉じた環境で利用できるという点だ。もし公開リポジトリでActionsを走らせてしまうと、ActionsのTerraformのログが見れてしまうことになるので、ここにうっかりセンシティブな値が入ると結構まずいことになる。

「非公開リポジトリならいいでしょ」と思うが、GitHubのリポジトリは必要になって一時的に公開することがあるので、AWSへのデプロイログみたいなのは個人的にあまり公開したくない。そこをAWSと密結合にかつ閉じた環境でできるというのは結構メリットが大きい。

どちらかというとCodePipelineは企業向けというより、個人~少人数向けのような気がする。企業向けならEnterpriseプランでActionsのリッチなVMや、大量の無料枠が使えるし、非公開リポジトリ設定も組織側で結構統制が取れる。しかし、個人だとそこらへん貧弱だったりガバガバになりがちなので、AWSへのデプロイをCI/CDをCodePipelineというのは結構ありな選択な気がした。

少なくとも自分が個人のリポジトリでAWSのCI/CDを動かすなら、多少課金があってもCodePipelineはありかなと思った。どっちかというとActionsでデプロイは個人だと危なっかしくてやりたくないというのが本音(バイナリのビルドぐらいだったら全然問題ないが)。CI/CDでのスクリプトの自由度はまだ見れてないので、今後使ってみたら評価変わるかもしれない。



Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です