kakakakakku blog

Weekly Tech Blog: Keep on Learning!

GitHub Actions の実践的なノウハウが凝縮されている素晴らしい一冊「GitHub CI/CD 実践ガイド」を読んだ

GitHub Actions の実践的なノウハウが凝縮されている一冊「GitHub CI/CD 実践ガイド」を読んだ📕

本書ではソフトウェア開発ライフサイクルから GitHub Actions 基礎トピック・GitHub Actions 実践トピックが紹介されていて,さらに GitHub Actions を活用して実現するリリース自動化・パッケージ管理・セキュリティのシフトレフトまでもカバーされている❗️素晴らしい👏 GitHub Actions をなんとなーく使っていたり,いつも既存のワークフローをコピーしていたりする人は必読かなと \( 'ω')/

また著者の経験に基づくベストプラクティス(こうすると良いよ〜的な)が散りばめられているのも現場目線で読めて良かった❗️

もちろん機能面に関しては GitHub Actions のドキュメントを読めば書いてあることも多いけど,すべてを読むのは大変だし,微妙に読みにくかったりもするし,書籍として体系的にまとまっていることはとても価値があると思う💡

docs.github.com

目次 🧩

基礎編

  • 第1章: ソフトウェア開発と GitHub
  • 第2章: GitHub Actions の基礎概念
  • 第3章: ワークフロー構文の基礎
  • 第4章: 継続的インテグレーションの実践
  • 第5章: 運用しやすいワークフローの設計
  • 第6章: アクションによるモジュール化

実践編

  • 第7章: クリーンなリポジトリの維持
  • 第8章: Dependabot による依存関係バージョンアップ
  • 第9章: GitHub Releases によるリリース自動化
  • 第10章: GitHub Packages によるパッケージ管理
  • 第11章: OpenID Connect によるセキュアなクラウド連携
  • 第12章: コンテナオーケストレーションのデプロイメント
  • 第13章: アクションのオープンソース化

応用編

  • 第14章: GitHub Actions の高度な使い方
  • 第15章: GitHub Actions のセキュリティ
  • 第16章: セキュリティのシフトレフト
  • 第17章: GitHub Apps トークンによるクロスリポジトリアクセス
  • 第18章: 継続的デリバリーの実践

あくまで個人的な感想だけど「第6章 (composite action)」「第14章 (reusable workflow)」は内容的に続いていても良いかな〜とは思った.

gihyo.jp

新たな発見もあった 🧩

僕自身は比較的 GitHub Actions の経験があって,復習も兼ねてザーッと読んだけど,それでも「知らなかったぞ〜💡」という情報も載っていて新たな発見もあった❗️具体的には以下など.

  • 中間環境変数テクニック(第3章)
  • Secrets のログマスクは回避できてしまう(第3章)
  • ログをグループ化して可読性を高める(第5章)
  • CODEOWNERS ファイル(第7章)
  • スクリプトインジェクション(第15章)
  • Gitleaks(第16章)

NAMEkakakakakku と設定して,Secrets のログマスクを回避する記法を試してみた.なるほどなぁー😅

jobs:
  secrets:
    runs-on: ubuntu-latest
    env:
      NAME: ${{ secrets.NAME }}
    steps:
      - run: echo ${NAME:0:1} ${NAME#?}

::group::{title} という記法でログをグループ化する Tips も知らなくて,実際に試してみた👌仕事でちょうど使えそうな部分があったからさっそく導入してみたりもした❗️

docs.github.com

jobs:
  grouping-log-lines:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Setup packages
        run: |
          echo '::group::pip install logs'
          pip install -r requirements.txt
          echo '::endgroup::'

GitHub Apps 解説

第17章「GitHub Apps トークンによるクロスリポジトリアクセス」に載っているクレデンシャルと GitHub Apps の解説は貴重だった❗️曖昧だった理解を整理できた.さらに classic Personal Access Tokens (PAT) は「最悪の選択肢」と書いてあって「それな〜😱」という気持ちに.Fine-grained Personal Access Tokens (PAT) に有効期限があるのは良いとしても,今もまだ Beta って書いてあって使いたくない気持ちがある💨

docs.github.com

その他読書メモ

第2章に workflow_dispatch の紹介が載っていて良かった.意外と知られてなくて無理矢理コミットしてトリガーしてる人をよく見る.あと本書には載ってなかったけど,workflow_dispatch はワークフローファイルがデフォルトブランチに存在してる前提で実行できるから,最初プルリクエストを送った段階だと試せなくてハマるという経験があったりする📝

docs.github.com

第2章の最後に「課金モデル」の解説が含まれているのも良かった💰同じく結構知られてない気がする(個人的な観測範囲内では).

docs.github.com

第4章で紹介されてた GitHub Actions の「concurrency 設定」は知っておくと便利❗️前にブログを書いた.

kakakakakku.hatenablog.com

もしかしたら「act」の紹介もあるかな〜と思って読み進めていたけど,本書には出ていなかったはず👀

github.com

kakakakakku.hatenablog.com

まとめ

GitHub Actions を使ってるチームは多いと思うけど,もっと GitHub Actions を知ることでさらに便利になって活用が進むと思う.本書には GitHub Actions の実践的なノウハウが凝縮されていて素晴らしい一冊だった📕 おすすめ \( 'ω')/

著者のブログ記事・本書のコードリポジトリもリンクしておくー🔗

nekopunch.hatenablog.com

github.com

ポスト

AWS Lambda 関数で Amazon ECR のイメージタグを上書きしたら AWS Lambda 関数を更新する

AWS Lambda 関数をコンテナイメージでデプロイしているときに,latest タグを使っていたり,同じタグを上書きして再利用していたりする場合がある.例えば AWS Well-Architected Framework「Container Build Lens」の設計原則 (Design principles) には「避けるべし!」と書いてあったりするけど,それでも現場ではそういう運用になっていることもある👀

The automatically created latest tag shouldn’t be used. A tagging strategy should produce immutable tags. Teams should avoid overwrite tags because this makes it hard to reproduce issues of specific application versions.

docs.aws.amazon.com

そのときに Amazon ECR のイメージタグを上書きしても AWS Lambda 関数に反映されなくて困ることがある.実は仕様はドキュメントに載っていて,AWS Lambda 関数にデプロイされたコンテナイメージはイメージダイジェストで決まっていて,Amazon ECR のイメージタグを上書きしても AWS Lambda 関数は自動的に更新されないようになっている.よって,意図的に AWS Lambda 関数を更新する必要がある👌

コンテナイメージとして定義された関数の場合、Lambda はイメージタグをイメージダイジェストに転換します。Amazon ECR では、イメージタグを新しいイメージに更新しても、Lambda は関数を自動的に更新しません。

docs.aws.amazon.com

検証

version 1

ドキュメントを参考に AWS Lambda 関数として Python コードを実行するコンテナイメージを作る.

docs.aws.amazon.com

今回は Amazon ECR のイメージタグを上書きして確認できれば良くて,lambda_function.py は以下のように単純に version 1 と出力するだけにした🙏

def handler(event, context):
    print('version 1')

さらに Dockerfile は以下のようにした.Python ベースイメージを使えば AWS Lambda RIC (Runtime Interface Clients) をセットアップしなくて OK👌

FROM public.ecr.aws/lambda/python:3.12

COPY lambda_function.py ${LAMBDA_TASK_ROOT}

CMD [ "lambda_function.handler" ]

コンテナイメージをビルドして,検証用の AWS Lambda 関数に latest タグとしてデプロイすると「イメージ URI」sha256:f9b01431b221a63d0360b4742c805997cbea81299264e4658774d2582ad0fc09 になっていた💡

AWS Lambda 関数を実行すると,期待通りに version 1 とログが出力される📝

version 2

次に lambda_function.py を以下のように更新して,コンテナイメージをビルドして latest タグを上書きする.

def handler(event, context):
        print('version 2')

もう一度 AWS Lambda 関数を実行すると,引き続き version 1 とログが出力されてしまう😅 そこで aws lambda update-function-code コマンドを実行して,上書きしたコンテナイメージのイメージダイジェストを指定する必要がある.

$ aws lambda update-function-code \
  --function-name sandbox \
  --image-uri 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/sandbox@sha256:10ad544f449ef5f2bb5a0228ffd8b71245f509a8fdc0ba92159c65f2f80fc363

さらにもう一度 AWS Lambda 関数を実行すると,期待通りに version 2 とログが出力される📝

まとめ

AWS Lambda 関数をコンテナイメージでデプロイするときは「Amazon ECR のイメージタグを上書きしても AWS Lambda 関数は自動的に更新されないようになっていること」を理解しておく必要がある👌

そして,AWS CodeBuild や GitHub Actions などを使ってコンテナイメージをビルドするときは一緒に aws lambda update-function-code コマンドを実行する必要がある \( 'ω')/

Zenn Book で「LocalStack 実践入門」を公開しました

今週月曜日(2024年8月5日)に Zenn Book で完全無料の学習コンテンツ「LocalStack 実践入門 | AWS アプリケーション開発ワークショップ」を公開しましたー🎉

AWS エミュレーターの LocalStack に実践的に入門するワークショップです❗️

zenn.dev

概要 🚀

アプリケーションを AWS 上で稼働させていて,マネージドサービスを中心に組み合わせたりすると,ローカル環境での開発がしにくく感じたり,AWS サービスに依存したアプリケーションコードの単体テストがしにくく感じることがあるはずです💡そんなときには LocalStack が便利だぞ〜👏という技術的な選択肢を紹介したく,ワークショップの開発を企画しました.

アプリケーションコードは Python (Boto3) を使っていて,単体テストは pytest を使っています.比較的簡単なコードですし,コード解説も載せているため,Python の経験がなくても大丈夫かなと思います👌

www.localstack.cloud

読者層 🎃

このワークショップは「LocalStack 未経験者」はもちろん「AWS 初学者」にもおすすめです❗️

僕自身は過去に AWS を教える仕事(テクニカルトレーナー)をしていて,現在も AWS 未経験者の多い組織では AWS 導入の支援や人材育成のサポートをしています.そういった経験の中でよく聞くのは「AWS を試しながら学習したいけど課金が怖い(無料利用枠もよくわからない)」という声です.もちろんある程度 AWS に慣れれば,料金ページや無料利用枠を理解しながら「試しながら学ぶ」というサイクルを回せますが,初学者にとっては第一歩を踏み出すこと自体にハードルの高さがあって〜という相談もよく受けます👀

LocalStack を使えば,AWS アカウントを作らずに AWS の学習ができます.macOS などのラップトップ上に「使い捨てできる自分専用の AWS 環境」を構築できるようなイメージです🌍 もちろん完璧なエミュレーターではないですし,サポートしている AWS サービスも限られていますが,それでも学習用途であれば十分かなと思います👌

docs.localstack.cloud

ワークショップ構成 🧪

ワークショップは Chapter.1 から Chapter.7 まであります.Chapter ごとにステップバイステップに進められて,Chapter を進めるごとにアーキテクチャも広がっていくように作っています.そして Chapter.8 は「応援購入」のための付録です.ワークショップに関連する小ネタを紹介しています.次のワークショップを企画するモチベーションにも繋がりますので,よろしければぜひ❗️

  • Chapter.1: ワークショップ環境をセットアップしよう
  • Chapter.2: LocalStack を使ってみよう
  • Chapter.3: Python コードで Amazon SQS と Amazon S3 を操作しよう
  • Chapter.4: AWS CloudFormation でデプロイを自動化しよう
  • Chapter.5: AWS SAM でサーバレスアプリケーションをデプロイしよう
  • Chapter.6: 使い捨ての LocalStack で単体テストを実行しよう
  • Chapter.7: Amazon API Gateway で API をデプロイしよう
  • Chapter.8: 付録(応援購入)

GitHub Codespaces 🌍

今回のワークショップでは「環境構築でつまづいて欲しくないな〜」ということを意識しました.そこでワークショップで使える統一的なオンライン環境として GitHub Codespaces を選んだところは工夫したポイントの一つで,ワークショップを進めやすくなっているはずです💪

github.co.jp

GitHub Codespaces では自動的に Python 3.12・AWS CLI・Docker・GitHub CLI を含んだ環境を起動するようにしています.

{
    "name": "aws-application-workshop-using-localstack",
    "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
    "features": {
        "ghcr.io/devcontainers/features/aws-cli:1": {},
        "ghcr.io/devcontainers/features/docker-in-docker:2": {},
        "ghcr.io/devcontainers/features/github-cli:1": {}
    }
}

ちなみに LocalStack・LocalStack AWS CLI・LocalStack AWS SAM CLI も GitHub Codespaces で自動的にセットアップできますが,LocalStack に関連するツールセットを意識することも重要かなと考えて,ワークショップでは意図的にセットアップしてもらうようにしています💡

github.com

GitHub Codespaces を使ったワークショップ(ハンズオン)の実施はとても便利で,今後もっと流行るかもしれないな〜と思っています❗️

Zenn Book 📕

今回のワークショップを企画するときに配信プラットフォームも悩みました.

ワークショップ以外に解説動画を編集して Udemy や YouTube に公開する案は,準備コストが大きすぎることとアップデートがしにくいことを懸念して避けました.また LocalStack に関する書籍を執筆する案も考えて,出版までの道のりが長すぎることと,今回書籍化するほどのボリュームではなかったこともあって避けました💨

最終的に Zenn Book を選びました.Zenn での執筆は初体験でしたが,Markdown 拡張・プレビュー機能・GitHub 連携など,執筆体験が良かったです❗️さらにコンテンツを基本無料で公開しつつ一部の Chapter のみ有料にしたり,バッジを贈れたり,執筆者のモチベーションに繋がる仕組みが整っているのも良いな〜と思いました.

まとめ 🐸

ぜひ「LocalStack 実践入門 | AWS アプリケーション開発ワークショップ」をお試しください❗️

好評であれば続編も考えたいな〜 \( 'ω')/

zenn.dev

ポスト 🦜

Terraform AWS Provider で AWS Chatbot を構築する

2024年8月2日にリリースされた Terraform AWS Provider v5.61.0 で AWS Chatbot の「Slack 設定 (SlackChannelConfiguration)」「Teams 設定 (MicrosoftTeamsChannelConfiguration)」を設定できるようになった❗️

github.com

背景

今までは AWS Provider を使って AWS Chatbot を構築できず,AWS Provider 側の issue は2020年3月から Open していた.

github.com

とは言え AWS Provider は AWS SDK for Go に依存していて,AWS SDK for Go で AWS Chatbot がサポートされていないという点が根本的な問題ではあった.

github.com

そして,2024年2月に AWS SDK for Go が AWS Chatbot をサポートしたため,AWS Provider もサポートできるようになったという背景がある.

github.com

今まで

Slack を例にすると,今までは以下のようなワークアラウンドを選択して AWS Chatbot を構築していたと思う.

仕事では AWS Cloud Control Provider (awscc) を導入していて,過去にブログにもまとめてある📝

kakakakakku.hatenablog.com

Terraform コード

さっそく AWS Provider でサポートされた aws_chatbot_slack_channel_configuration リソースを試す👌 以下のように実装した.IAM Role と Amazon SNS トピックは省略する.

resource "aws_chatbot_slack_channel_configuration" "main" {
  configuration_name = "sandbox"
  iam_role_arn       = aws_iam_role.chatbot.arn
  slack_team_id      = "XXXXXXXXX"
  slack_channel_id   = "XXXXXXXXX"
  sns_topic_arns     = [aws_sns_topic.chatbot.arn]
}

簡単だ \( 'ω')/

動作確認

AWS Chatbot の「テストメッセージを送信」と Amazon SNS の「メッセージの発行」を実行して,期待通り Slack に通知された👏

ちなみに Amazon SNS から送信するときは以下のドキュメントを参考にカスタムフォーマットを使った.

{
    "version": "1.0",
    "source": "custom",
    "content": {
        "description": ":tada: Terraform AWS Provider v5.61.0 now supports the `aws_chatbot_slack_channel_configuration` resource! yay!"
    }
}

docs.aws.amazon.com

補足: SlackWorkspaceIdSlackTeamId

Slack Workspace ID を指定するときに,AWS Cloud Control Provider (awscc) の場合は AWS CloudFormation のリソースに対応しているため SlackWorkspaceId を設定するけど,AWS Provider の場合は slack_team_id を設定するという違いがあった.ちなみに AWS Chatbot API のリクエストパラメータは SlackTeamId になっていた.

docs.aws.amazon.com

docs.aws.amazon.com

ポスト

AWS Step Functions で AWS Lambda 関数を使わず Amazon S3 にオブジェクトをアップロードする

AWS Step Functions でワークフローを構築しているときに AWS Step Functions の実行時に渡すインプットの一部をファイルとして Amazon S3 に保存したいという場面があったりする❗️実際に最近あった \( 'ω')/

もちろん AWS Lambda 関数を追加すれば柔軟で自由度高くワークフローを拡張できるけど,比較的シンプルな処理であれば無理に AWS Lambda 関数を増やすのではなく,AWS Step Functions の「AWS SDK 統合」を活用する方が良かったりする.ちなみにシンプルな処理であれば AWS Lambda 関数は不要というベストプラクティスは「直接統合 (Direct Integrations)」という表現で AWS Well-Architected Framework Serverless Applications Lens に載っている👌

docs.aws.amazon.com

ワークフロー

今回は動作確認のために AWS SDK 統合 s3:PutObject のみを設定する.

そして,アップロードする Amazon S3 バケットのオブジェクトキーは AWS Step Functions の実行名とインプットとして渡す name を組み合わせるため,AWS Step Functions の組み込み関数 States.Format と AWS Step Functions のコンテキストオブジェクト $$.Execution.Name を設定する.

docs.aws.amazon.com

docs.aws.amazon.com

今回は AWS CDK でワークフローを実装した.

import {
  Stack,
  StackProps,
  aws_s3,
  aws_stepfunctions,
  aws_stepfunctions_tasks,
} from 'aws-cdk-lib'
import { Construct } from 'constructs'

export class SandboxCdkStepfunctionsPutObjectStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props)

    const bucket = new aws_s3.Bucket(this, 'Bucket', {
      bucketName: 'kakakakakku-sandbox-stepfunctions-put-object',
    })

    const putObjectTask = new aws_stepfunctions_tasks.CallAwsService(this, 'PutObjectTask', {
      service: 's3',
      action: 'putObject',
      parameters: {
        'Bucket': bucket.bucketName,
        'Key.$': `States.Format('{}/{}', $$.Execution.Name, $.name)`,
        'Body.$': '$.body',
        'ContentType': 'application/json',
      },
      iamResources: ['*'],
    })

    new aws_stepfunctions.StateMachine(this, 'SandboxCdkStepfunctions', {
      stateMachineName: 'sandbox-cdk-stepfunctions-put-object',
      definitionBody: aws_stepfunctions.DefinitionBody.fromChainable(putObjectTask),
    })
  }
}

最終的にデプロイされたワークフロー定義は以下👌

{
  "StartAt": "PutObjectTask",
  "States": {
    "PutObjectTask": {
      "End": true,
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
      "Parameters": {
        "Bucket": "kakakakakku-sandbox-stepfunctions-put-object",
        "Key.$": "States.Format('{}/{}', $$.Execution.Name, $.name)",
        "Body.$": "$.body",
        "ContentType": "application/json"
      }
    }
  }
}

動作確認

2種類のインプットを準備して,ワークフローを実行する❗️

{
    "name": "sandbox001.json",
    "body": {
        "title": "title001",
        "body": "body001"
    }
}
{
    "name": "sandbox002.json",
    "body": {
        "title": "title002",
        "body": "body002"
    }
}

すると,期待通りに Amazon S3 バケットにオブジェクトが追加されている👌

$ aws s3api list-objects --bucket kakakakakku-sandbox-stepfunctions-put-object | jq -r '.Contents[].Key'
f32ea288-3103-4c8a-9b65-6c57298420f8/sandbox002.json
f5f16907-279b-447a-9e1c-eddc44ea78e3/sandbox001.json

そして f5f16907-279b-447a-9e1c-eddc44ea78e3/sandbox001.json を開くと,期待通りにインプットの body に指定した JSON になっていた👌

{"title":"title001","body":"body001"}