Github Actions 의 crontab 스케쥴링 기능으로 외주 프로젝트의 Certbot SSL 인증서 재발급 자동화하기

Github Actions 의 crontab 스케쥴링 기능으로 외주 프로젝트의 Certbot SSL 인증서 재발급 자동화하기

Github Actions 에서의 crontab 스케쥴링?

Github Actions은 crontab scheduling 을 지원한다. 적당히 간단한 job 을 주기적으로 돌리고 싶을 때, aws 에서 cron event 로 lambda 를 트리거하는 방법을 선택할 수도 있겠다. 하지만 이는 lambda 를 위한 추가적인 구현 수정이 필요하고 aws를 잘 모르는 개발자라면 이유모를 비용 청구에 대한 두려움이 있을 것이다. (사실 비용 청구는 전혀 문제되지 않을 것이다. AWS lambda의 비용청구는 100만건 단위로… 자세한 것은 문서를 참고하기 바란다.)

항시 켜두는 리눅스 서버에 cron 데몬에 의존하는 방식도 부담스럽다. 언제 꺼질지 모르는 심리적/물리적 관리비용이 있다. Github Action 의 cron 스케쥴링을 이용하면 코드 수정 없이 간단하게 문제를 해결할 수 있다. 단, cold start 시간이 좀 걸리긴 하지만, 맘편히 쓸 수 있는 옵션이다.

이번에 github action을 도입하게 된 건 외주 프로젝트다. docker-compose 와 Server-side rendering django 어플리케이션으로 이루어진 웹의 HTTPS 를 위한 SSL 인증서 만료 문제를 해결하기 위해서 github action을 사용하기로 했다. 원래는 이를 인스턴스의 cron 데몬으로 certbot 인증서 재발급을 해줬는데 원인불명의 이유로 잘 작동하던 cron 이 동작하지 않아 웹 접근에 문제가 되었다.

그래서 어떻게 쓰는건데?

아래와 같은 파일을 `.github/workflow/refresh.yml` 정도로 저장하여 git push 만 해주면 된다. 알아서 잘 돈다. cron 공식을 모르면 이런 사이트에서 원하는 크론 문법을 얻길 바란다.

name: "HTTPS REFRESH"
on:
  schedule:
    - cron:  "0 0 1 * *"
jobs:
  codeTest:
    name: RUN REFRESH
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: SAVE KEY
        run: |
          unzip -P ${{ secrets.ZIP_PASSWORD }} pem.zip
          chmod 400 <PEM>.pem
          mkdir ~/.ssh
          touch ~/.ssh/known_hosts
        env:
          DEV_ENV_FILE : ${{ secrets.PEM }}
      - name: RUN REFRESH SCRIPT.
        run: |
          ssh-keyscan -H <DOMAIN> > ~/.ssh/known_hosts
          ssh -t -t -i <PEM>.pem <DOMAIN> "sudo ./refresh-letsencrypt.sh"
      - name: DELETE KEY.
        run: rm <PEM>.pem

위 github action이 돌면서 문제를 해결했다. 이 깃헙 액션은 아래처럼 동작한다.

SSL 인증서 갱신을 위한 bash 스크립트.

이 스크립트는 오랫동안 사용해온 스크립트이다. github action에서는 SSH로 해당 인스턴스에 저장된 아래 스크립트를 실행한다.

#!/bin/bash

domains=(<DOMAIN.ORG>)
rsa_key_size=4096
data_path="./data/certbot"
email="byun@<DOMAIN.ORG>"
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits

if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
  echo "### Downloading recommended TLS parameters ..."
  mkdir -p "$data_path/conf"
  curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
  curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
  echo
fi

echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker-compose run --rm --entrypoint "\
  openssl req -x509 -nodes -newkey rsa:1024 -days 1\
    -keyout '$path/privkey.pem' \
    -out '$path/fullchain.pem' \
    -subj '/CN=localhost'" certbot
echo


echo "### Starting nginx ..."
docker-compose up --force-recreate -d nginx
echo

echo "### Deleting dummy certificate for $domains ..."
docker-compose run --rm --entrypoint "\
  rm -Rf /etc/letsencrypt/live/$domains && \
  rm -Rf /etc/letsencrypt/archive/$domains && \
  rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo


echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
  domain_args="$domain_args -d $domain"
done

# Select appropriate email arg
case "$email" in
  "") email_arg="--register-unsafely-without-email" ;;
  *) email_arg="--email $email" ;;
esac

# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi

docker-compose run --rm --entrypoint "\
  certbot certonly --webroot -w /var/www/certbot \
    $staging_arg \
    $email_arg \
    $domain_args \
    --rsa-key-size $rsa_key_size \
    --agree-tos \
    --force-renewal" certbot
echo

echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload

간단한 결론

비개발자만 있는 클라이언트의 외주는 더더욱 상황에 맞는 알맞은 도구를 적절히 잘 써서 오버엔지니어링을 피하되, 최대한 사후 관리와 모니터링을 신경쓰는 것이 모두에게 평화를 가지고 올 것이다.

깃헙 액션은 누구나 링크를 타고 들어오면 쉬운 화면으로 성공/실패 여부를 확인할 수 있다. 심지어, 웹 문서에 badge 를 첨부해 어디서든 결과를 확인할 수 있다. 관리자의 입장에서도, 로그를 쉽게 확인할 수 있기 때문에 어디서 문제가 생겼는지 파악하기 매우 용이하다.

ps.아무리 그래도 bash보단 python 스크립팅이 훨씬 쉽다.