Material for MkDocs をAWS CDKとGitHub Actionsで公開する
当社は夏合宿が無事に終了し、ひと夏の思い出となってしまいました。やっさんでございます。
この度、Immersion Daysコンテンツを作成するのに利用しているMaterial for MKDocsの公開環境をAWS CDKとGitHub Actionsで共通化しましたので、その内容をご紹介します。
当社におけるMaterial for MKDocsの利用シーンについて
当社はAmazon Web Services様(以下AWS)のImmersion Daysコンテンツを作成する機会が度々あります。Immersion Daysコンテンツはスライド資料はもちろんのこと、ワークショップコンテンツを準備し参加者の皆様に実際にAWSを使って頂く機会を提供しております。
当社ではワークショップコンテンツの手順書を作成する際にオープンソースソフトウェア(OSS)のMaterial for MKDocsを利用しておりまして、手順書を公開する環境として Amazon CloudFront と CloudFront Functions を活用しております。
Material for MKDocs 公開環境の共通化
上記の利用シーンの背景がありワークショップコンテンツの構築頻度が増えておりまして、IaC化とGitHub Actionsによる自動デプロイの仕組みを用いることで公開環境の共通化を図りました。本記事ではこの公開環境の共通化について具体的に紹介します。
サンプルのソースコードはGitHubにございます。
AWS CDKを利用した公開環境のプロビジョニング
CloudFrontを活用した手順書の公開環境はAWS CDKによって簡単にデプロイ出来るようにしました。AWS CDKで構築するAWSリソースは以下が含まれます。
AWS リソース種別 | 説明 |
---|---|
Amazon S3 バケット | コンテンツのオリジンを保存するS3バケット。 MKDocsでビルドされた静的ファイルを配置します。 |
Amazon S3 バケットポリシー | CloudFrontのディストリビューションから アクセス可能となるよう設定するバケットポリシー。 |
Amazon CloudFront オリジンアクセスコントロール | CloudFrontからS3のオリジンを 参照できるようにする設定。 |
Amazon CloudFront ディストリビューション | CloudFrontのメインリソース。ファンクションの 紐づけやデフォルトのビヘイビアを含みます。 |
Amazon CloudFront ファンクション | 限定された方が閲覧可能とするための Basic認証の設定と、index.html の パス置換のために利用します。 |
重要な箇所について以下に解説いたします。
Amazon S3 バケット
S3バケットはパブリックアクセスをブロックしCloudFront経由からのみアクセスできるようにします。
const originS3Bucket = new aws_s3.Bucket(this, 'MKDocsOriginS3Bucket', {
bucketName: `${cdk.Stack.of(this).account}-mkdocs-origin`,
websiteIndexDocument: 'index.html',
blockPublicAccess: aws_s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: RemovalPolicy.DESTROY,
});
Amazon CloudFront オリジンアクセスコントロール
今回はオリジンアクセスアイデンティティ(OAI)ではなく、オリジンアクセスコントロール(OAC)を利用しました。以下はS3バケットをオリジンにするためのOACになります。
const originAccessControl = new aws_cloudfront.CfnOriginAccessControl(this, 'MKDocsOriginAccessControl', {
originAccessControlConfig: {
name: 'MKDocsOriginAccessControlForOriginS3Bucket',
originAccessControlOriginType: 's3',
signingBehavior: 'always',
signingProtocol: 'sigv4',
description: 'Access Control',
},
});
また、CloudFront ディストリビューションへの紐づけも容易でして、作成したCloudFront ディストリビューションに後付で付与しました。以下が記述例になります。
const cfnDistribution = distribution.node.defaultChild as aws_cloudfront.CfnDistribution
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.OriginAccessControlId', originAccessControl.getAtt('Id'))
Amazon CloudFront ディストリビューション
CloudFront ディストリビューションでは、コードの4行目でS3バケットをオリジンに指定します。また、8行目でBasic認証のCloudFront ファンクションの割り当て、16行目でHTTPをHTTPSにリダイレクトするビューワープロトコルポリシーも設定しています。
const distribution = new aws_cloudfront.Distribution(this, 'MKDocsDistribution', {
defaultRootObject: 'index.html',
defaultBehavior: {
origin: new aws_cloudfront_origins.S3Origin(originS3Bucket),
functionAssociations : [
{
eventType: FunctionEventType.VIEWER_REQUEST,
function: new aws_cloudfront.Function(this, 'MKDocsBasicAuthFunction', {
functionName: `mkdocs-basic-authentication`,
code: aws_cloudfront.FunctionCode.fromFile({
filePath: "basic_auth_function/index.js"
}),
}),
},
],
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS
},
});
Amazon CloudFront ファンクション
CloudFront ファンクションでは、Basic認証とパス置換をビューワーリクエストに対して行います。CloudFront ファンクションのコードは以下の通りです。尚、Basic認証のユーザー名とパスワードはBase64でエンコードしております。
なぜパス置換が必要なのか。
Cloud Front の オリジンS3 における index.html の扱いについて。
CloudFrontはオリジンのルートディレクトリにある index.html は認識しますが、階層構造のルートディレクトリより先の階層の index.html は認識してくれません。階層のみのURLでアクセスするとオブジェクトが存在しない旨のエラーが表示されます。
MKDocsはビルドするとindex.html が各階層に出力されるため、上記のエラーが発生します。
エラーを解消するために、CloudFront ファンクションの20-24行目の箇所で、アクセス時に index.html を付与するよう変更しています。
function handler(event) {
var request = event.request;
var headers = request.headers;
var uri = request.uri;
// echo -n user:pass | base64
var authString = "Basic dXNlcjpwYXNz";
if (
typeof headers.authorization === "undefined" ||
headers.authorization.value !== authString
) {
return {
statusCode: 401,
statusDescription: "Unauthorized",
headers: { "www-authenticate": { value: "Basic" } }
};
}
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
GitHub Actionsを活用した継続的デプロイ
AWS CDKによって、公開環境をプロビジョニングする準備が出来ました。次に、GitHub Actionsによってデプロイを自動化します。
OIDCを利用したAWSアクセスキーの秘匿化
現在はGitHub ActionsからAWSにアクセスするためのアクセスキーとシークレットキーの取得はOpenID Connect(OIDC)を利用することが望ましいです。
OIDCを利用することでAWSにアクセスするためのアクセスキーの管理から開放されます。本記事ではGitHub ActionsのSecretsにOIDCのためのIAMロールのArnを指定することで実現しています。
必要な環境変数
以下の環境変数をGitHub ActionsのリポジトリSecretsとして設定します。
Secrets名 | 説明 |
---|---|
S3_BUCKET_NAME | AWS CDKで作成したオリジンのS3のバケット名 |
AWS_REGION | AWSのリージョン |
AWS_OIDC_IAM_ROLE_ARN | AWSアカウントで準備したOIDCを許可する IAM RoleのロールArn |
SLACK_WEBHOOK_URL | Slackに通知するためのWebhook URL |
SLACK_CHANNEL_ID | Slackに通知するための通知先のチャンネルID |
Material for MKDocsのコンテナイメージを活用した継続的デプロイ
GitHub ActionsのワークフローにはMaterial for MKDocsのコンテナイメージを利用します。コンテナイメージを利用するメリットとして、 Pythonのrequirements.txt のバージョンに固定されることなく、最新のMaterial for MKDocsを利用してビルド可能であるため、機能が改善されたバージョンをデプロイすることが出来ます。
※逆の意味ではMaterial for MKDocsのバージョンを固定していないため、予期せぬ表示崩れが発生する可能性があります。後方互換性を重視するのであればコンテナイメージのタグを固定することが望ましいです。
ワークフローのコード例は以下のようになります。6~10行目でSecrets情報を参照しております。23~28行目ではコンテナイメージを利用してMKDocsをビルドしています。30~34行目ではOIDCを利用してAWSの認証情報取得を行っております。36~38行目ではビルドされたコンテンツをS3バケットに同期しています。
name: "Manual Deploy"
on: workflow_dispatch
env:
BUCKET_NAME : ${{ secrets.S3_BUCKET_NAME }}
AWS_REGION : ${{ secrets.AWS_REGION }}
AWS_ROLE_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
permissions:
id-token: write
contents: read
jobs:
build_and_push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
uses: addnab/docker-run-action@v3
with:
image: squidfunk/mkdocs-material:latest
options: -v ${{ github.workspace }}:/docs
run: mkdocs build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ env.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Sync html to s3
run: |
aws s3 sync ./site s3://${{ env.BUCKET_NAME }}/ --delete --quiet
- name: Slack Notification on Success
if: success()
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: ${{ env.SLACK_CHANNEL_ID }}
SLACK_TITLE: MKDocs - Manual Deploy job Success
SLACK_COLOR: good
SLACK_MESSAGE: Successfully executed the Manual Deploy job.
SLACK_USERNAME: MKDocs - Manual Deploy job Success
- name: Slack Notification on Failure
uses: rtCamp/action-slack-notify@v2
if: failure()
env:
SLACK_CHANNEL: ${{ env.SLACK_CHANNEL_ID }}
SLACK_TITLE: MKDocs - Manual Deploy job Failure
SLACK_COLOR: danger
SLACK_MESSAGE: <!channel> The Manual Deploy job failed to execute.
SLACK_USERNAME: MKDocs - Manual Deploy job Failure
今回はCloudFrontのキャッシュ削除(Invalidation)は行っておりませんが、デプロイ時にキャッシュを削除し最新のコンテンツを取得できるにしたい場合はキャッシュ削除の実行を推奨いたします。
以下画像のように、Material for MKDocsのデプロイに成功しました。
また、Slackにも成功が通知され、コンテンツも公開されました。
まとめ
本記事ではAWS CDKとCloudFrontを用いたMaterial for MKDocsの公開環境をご紹介しました。Material for MKDocsはエンジニアが慣れ親しんでいるMarkdown記法で記述できることがメリットの一つとしてあります。
ご興味がありましたら、是非とも本記事の公開環境の構築をお試し下さい。