突然ですが、 GCE の Container-Optimized OS で、定期実行がしたいです。より具体的には、定期的に Let's Encrypt の certbot が動かしたいのです。
調べたところ、
- (アプリケーションサーバとか)使う連中のどこかに cron をインストールして一緒に動かす
- cron をインストールしたコンテナを作ってがんばる
- tasker コンテナを使う
みたいなのが出てきます。一番上はちょっと…と思い、他二つをがんばってみましたが、なんでか苦労するのですよね。たかが定期実行したいだけなのに…。
だったら雑に、無限ループで sleep
とコマンド実行をし続けるプログラムを書いちゃうほうが楽じゃないか、と思いました。
要件
- Docker コンテナを使って任意のコマンドを定期実行したい
- cron のように「何時何分に実行したい」のではなく、概ね一定間隔で実行されていれば厳密性は問わない
全く何も考えずに書くとつまりこういうことがしたい。
package main
import (
"fmt"
"os/exec"
"time"
)
func main() {
for {
out, err := exec.Command("echo", "hello").Output()
fmt.Printf("%v", string(out))
if err != nil {
fmt.Printf("[error] %v\n", err)
}
time.Sleep(5 * time.Second)
}
}
あとはこれがコンテナで実行できれば OK です。簡単。
せめて実行間隔や実行コマンドは外から指定したい、みたいなことを思っていくつか機能を足してからコンテナ化しました。
使い方
こんなふうに使えます。
docker run hiko/interval-runner:1.0.0 --interval 5s echo Hello World
--interval
オプションで実行間隔が指定できます。フォーマットはtime.ParseDuration
(godoc)に従います。
certbot (docker)を実行したい
certbot のコンテナは、こんな感じに実行しています。
docker run --rm -it --name certbot \
-v $PWD/letsencrypt/log:/var/log/letsencrypt \
-v $PWD/letsencrypt/etc:/etc/letsencrypt \
-v $PWD/letsencrypt/html:/var/www/html \
certbot/certbot:v1.26.0 certonly --webroot -w /var/www/html -d example.com
つまり docker
コマンドを使いたいので、インストールが必要です。
こんな感じの Dockerfile を用意して手順通りに docker-ce-cli をインストールし(最新のインストール手順は公式ドキュメントを参照してください)
FROM hiko/interval-runner:1.0.0
# Install docker client
RUN apt-get update -y && \
apt-get install -y ca-certificates curl gnupg lsb-release
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
RUN apt-get update && apt-get install -y docker-ce-cli
VOLUME ["/var/run/docker.sock"]
たとえば docker-runner としてビルドし
docker build -t docker-runner .
こんなふうに実行できます。
docker run --rm --name docker-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $PWD/letsencrypt/log:/var/log/letsencrypt \
-v $PWD/letsencrypt/etc:/etc/letsencrypt \
-v $PWD/public:/var/www/html \
docker-runner \
--interval 168h \
docker run --rm --name certbot \
-v /var/log/letsencrypt:/var/log/letsencrypt \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/www/html:/var/www/html \
certbot/certbot:v1.26.0 certonly --webroot -w /var/www/html -d example.com
docker run
が多段なのでわかりにくいけれど、まあうん、よく見ればわかる。なんとか。
ちなみに docker クライアントを入れているけれど、このためにコンテナが肥大します。run
するだけならクライアントを介さず UNIX ドメインソケットに直接書き込めば良いんじゃないかなと思います(REST APIなので、身構えなくても大丈夫です)。
コードは、絶対何か見落としがあるだろうと直感できるくらいに簡単なので、無理してベースコンテナなどを使わず、自作しても良いと思います。
docker-compose に仕掛けたい
nginx をリバースプロキシにしつつ、配下にアプリケーションサーバを置き、さらに certbot の定期実行を仕掛けておきたい、というのが今回の話です。
こんな感じの docker-compose.yml
になりました。
version: "3"
services:
proxy:
image: nginx:1.21.6-alpine
ports:
- "80:80"
- "443:443"
volumes:
- $PWD/config/nginx.conf:/etc/nginx/conf.d/default.conf
- $PWD/log:/var/log/nginx
- $PWD/public:/app/public
restart: always
backend:
image: webapp:latest
ports:
- "8001:8001"
volumes:
- $PWD/log:/app/log
- $PWD/data:/app/data
restart: always
certbot_runner:
image: docker-runner:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- $PWD/letsencrypt/log:/var/log/letsencrypt
- $PWD/letsencrypt/etc:/etc/letsencrypt
- $PWD/public:/var/www/html
command: [
"--initial-wait", "1m",
"--interval", "168h",
"docker", "run", "--rm", "--name", "certbot",
"-v", "/var/log/letsencrypt:/var/log/letsencrypt",
"-v", "/etc/letsencrypt:/etc/letsencrypt",
"-v", "/var/www/html:/var/www/html",
"certbot/certbot:v1.26.0", "certonry", "--webroot", "-w", "/var/www/html", "-d", "example.com"
]
nginx よりも先に certbot が動くと困るので、 --initial-wait
なる雑なオプションを作って少し待たせています。ちょっと使いたいだけだったため、がんばりたくなかったのです…。
一方で、 certbot が一度も動かない状態で nginx を起動しようとすると、 letsencrypt/live/*
がなくてエラーが起きますね。まあどうせ最初は Agreement とかもありますから、初回だけは手でやってそうですが(と思ったのでスキップするオプションを使っていないですが)、注意しましょう。
まとめ
cronで苦労するなら作っちゃうのはどうでしょう。