はじめに
Hugo(gohugo.io)は静的コンテンツジェネレータとして、kubernetes.ioなどのWebサイトを構築するために広く使われています。
通常は手作業で更新、rsyncでWebサーバーに更新といった使い方をすれば良いのですが、外部データベースからの最新データを反映してページを生成したり、RSSフィードと連携してニュース記事などを掲載していると、自動的かつ定期的に静的ページを更新するニーズがあります。
そこでKubernetesのCronJobオブジェクトを利用するため、hugoが稼動するDockerコンテナを生成しました。比較的新しいコードベースを利用しているため、hugoはソースコードからビルドします。
ワークフロー
ここで対象とする主な処理の流れは以下のとおりです。
- ローカルのGitLabに登録しているHugoプロジェクトがある
- Dockerコンテナが起動時に、Hugoプロジェクトをgit cloneし、静的コンテンツを生成する
- public/ に生成されたコンテンツを、rsyncでリモートサーバーに転送する
GitHubでも同様に動作するはずですし、Hugoをubuntuのdeb packageを利用するなどすれば、わざわざメモを残すほどのことではありませんが、hugoの良いdockerコンテナもなさそうだったのでレシピを残しておきます。
前提
以下のような環境を想定しています。
- Hugoプロジェクトでは、build時にgetJSONなどで外部データソースと連携し、変化するコンテンツを生成している
- GitLabは、PrivateプロジェクトをSSH経由でcloneすることを想定している
- 公開プロジェクトであれば、httpsプロトコルも利用可能です
- GitLabのSSH Keyと、リモートホストのauthorized_keysには、同じSSH公開鍵の情報を登録している
- つまり、gitlabとリモートホストへのアクセス時には、同じSSH秘密鍵(/app/id_ed25519)を利用している
準備したファイルたち
この作業に必要なファイルは以下のとおりです。SSH鍵は既存のものを使う場合があると思うので、実行時に補足しています。
Dockerfile
alpineとmulti-stage buildを利用して、最終的なコンテナイメージは50〜60MB程度のサイズになっています。
asciidoctorはmarkdownよりも、AsciiDocを好んで使っているので加えていますが、不要であれば省いてください。
Hugoテーマも利用しているので、冗長になっていますが、不要であれば、run.sh
を調整してから編集してください。
FROM golang:1.19-alpine3.16 as hugodev
RUN apk --no-cache add git make gcc g++ libc-dev patch
ENV GOPATH /root/go
RUN mkdir ${GOPATH}
RUN mkdir /work
WORKDIR /work
RUN git clone https://github.com/gohugoio/hugo.git
WORKDIR /work/hugo
RUN git checkout refs/tags/v0.108.0 -b t_v0.108.0
RUN go install --tags extended
FROM alpine:3.16
RUN apk update && \
apk add --no-cache tzdata bash ca-certificates rsync openssh git libstdc++ asciidoctor
COPY --from=hugodev /root/go/bin/hugo /usr/local/bin/hugo
ADD run.sh /run.sh
RUN chmod +x /run.sh
ENV HUGO_GITLAB_SSHKEY_FILEPATH "/app/id_ed25519"
ENV HUGO_GITLAB_PROJECT_URL "git@github.com:YasuhiroABE/example-hugo-project.git"
ENV HUGO_GITLAB_PROJECT_NAME "example-hugo-project"
ENV HUGO_GITLAB_THEME_URL "git@github.com:YasuhiroABE/example-hugo-theme.git"
ENV HUGO_GITLAB_THEME_NAME "theme/mytheme"
ENV HUGO_CONTENTS_DEST_PATH "user01@example.com:public_html/."
RUN addgroup hugo
RUN adduser -S -G hugo hugo
RUN mkdir /work
RUN chown hugo:hugo /work
USER hugo
WORKDIR /work
ENTRYPOINT ["/run.sh"]
Hugoのバージョンは、0.108.0を指定してますが、任意の値に変更してください。
run.sh
インタラクティブにTTY経由でコマンドを実行する場合と比べると、バッチジョブ特有のオプション指定などを行なっています。
また、環境によってrsyncのオプションなどは変化すると思いますので適宜調整してください。
#!/bin/bash -x
## omitting host-key check
env GIT_SSH_COMMAND="ssh -i ${HUGO_GITLAB_SSHKEY_FILEPATH} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
git clone "${HUGO_GITLAB_PROJECT_URL}" ${HUGO_GITLAB_PROJECT_NAME}
## update the hugo theme
cd "${HUGO_GITLAB_PROJECT_NAME}"
rm -rf "${HUGO_GITLAB_THEME_NAME}"
## build static-contents by hugo
env GIT_SSH_COMMAND="ssh -i ${HUGO_GITLAB_SSHKEY_FILEPATH} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
git clone "${HUGO_GITLAB_THEME_URL}" "${HUGO_GITLAB_THEME_NAME}"
hugo
## transfer static contents to the remote host
exec rsync -rcv -e "ssh -i ${HUGO_GITLAB_SSHKEY_FILEPATH} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
public/. "${HUGO_CONTENTS_DEST_PATH}"
利用例
Dockerコンテナのビルドと、DockerHubへの登録
Dockerfileとrun.shを配置してから、通常の方法でbuild、tag, pushなどの操作が可能です。
<username>
の部分は、DockerHub上のUsernameです。
$ sudo docker build . --tag hugo-rsync:1.0.0 --no-cache
$ sudo docker tag hugo-rsync:1.0.0 docker.io/<username>/hugo-rsync:1.0.0
$ sudo docker push docker.io/<username>/hugo-rsync:1.0.0
実行は次のような操作で可能です。ssh-keygenコマンドは最初の1回だけ必要ですし、既に利用している鍵ファイルを利用する場合には、conf/id_ed25519
の名称で配置してください。Dockerfile上の HUGO_GITLAB_SSHKEY_FILEPATH
で指定した値と整合を取る必要があります。
$ mkdir conf
$ ssh-keygen -t ed25519 -f conf/id_ed25519
$ sudo docker run -it --rm \
--env HUGO_GITLAB_PROJECT_URL="<gitlab/github clone url>" \
--env HUGO_GITLAB_PROJECT_NAME="<directory name of the cloned hugo project>" \
--env HUGO_CONTENTS_DEST_PATH="<username>@<hostname>:<dest-path>" \
--env HUGO_GITLAB_THEME_URL="https://github.com/theNewDynamic/gohugo-theme-ananke.git" \
--env HUGO_GITLAB_THEME_NAME="themes/ananke" \
-v `pwd`/conf:/app --name hugo-rsync hugo-rsync:latest
環境によってrun.shの内容はかなり変わりそうですが、getJSONなどを多用するコンテンツ生成機能を利用している場合には、バックグラウンドでコンテンツが更新できることはそれなりに便利だと思われます。
KubernetesのCronJobからの実行
実際のDockerHubのコンテナはPrivate扱いなので、このままでは実際の利用はできない点に注意してください。
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hugo-rsync
spec:
schedule: "45 5 * * *"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 30
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
imagePullSecrets:
- name: regcred
containers:
- name: crawler-scheduler-cron
image: yasuhiroabe/my-hugo-rsync:1.0.0
env:
- name: LC_CTYPE
value: ja_JP.UTF-8
- name: HUGO_GITLAB_PROJECT_URL
value: "ssh://git@example.com:30001/user01/hugo-contents.git"
- name: HUGO_GITLAB_PROJECT_NAME
value: "hugo-contents"
- name: HUGO_CONTENTS_DEST_PATH
value: "user01@example.net:/home/web-int/labs/opm/public_html/."
- name: HUGO_GITLAB_THEME_URL
value: "ssh://git@example.com:30001/user01/hugo-mytheme.git"
- name: HUGO_GITLAB_THEME_NAME
value: "themes/mytheme"
volumeMounts:
- name: ssh-keys
mountPath: /app
readOnly: true
volumes:
- name: ssh-keys
secret:
secretName: ssh-seckey
defaultMode: 0444
鍵ファイルはあらかじめ環境で生成し、secretオブジェクトとして登録しています。
$ mkdir conf
$ ssh-keygen -t ed25519 -f conf/id_ed25519
$ sudo kubectl -n mynamespace create secret generic ssh-seckey --from-file=conf/id_ed25519
NS(namespace)名(mynamespace)は適宜変更してください。
conf/id_ed25519.pub の内容は、GitLab/GitHubのSSH Keys, リモートホストのauthorized_keysの両方に登録する必要があります。
SSH秘密鍵のパーミッションが格好悪いですが、どうせ外部からアクセスできないので諦めました。
ここまでで、k8s環境で定期的にコンテンツを更新する仕組みが構築できました。
以上