쿠버네티스에 Jekyll 블로그 셀프호스팅 하기
나는 기억력이 좋지 않아 한번 했던 것도 금세 까먹곤 하는 편이라, 평소 작업 내용이나 학습 노트, 아이디어 등을 노트로 정리해두고 비슷한 경우가 발생했을 때 다시 찾아보곤 한다. 아무래도 익숙하기도 하고, 애플리케이션 간에 Integration에도 유리한 면이 있어 markdown 포맷을 애용하곤 하는데, 기존에 작성해둔 내용을 재활용할 순 없을까 고민하다 발견한 게 Jekyll이다.
Jekyll은 표방하는 것 그대로 텍스트 파일을 정적사이트로 변환해주는 툴이다. Jekyll 프로젝트의 기본적인 구조를 보면 아래와 같은데,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.
├── _config.yml
├── _data
│ └── members.yml
├── _drafts
│ ├── begin-with-the-crazy-ideas.md
│ └── on-simplicity-in-technology.md
├── _includes
│ ├── footer.html
│ └── header.html
├── _layouts
│ ├── default.html
│ └── post.html
├── _posts
│ ├── 2007-10-29-why-every-programmer-should-play-nethack.md
│ └── 2009-04-26-barcamp-boston-4-roundup.md
├── _sass
│ ├── _base.scss
│ └── _layout.scss
├── _site
├── .jekyll-metadata
└── index.html # 올바른 머리말을 가진 'index.md' 도 가능
/_post
에 포스팅할 텍스트 파일을(markdown 뿐만이 아니라 Liquid, html, css 도 지원한다.) 넣고 bundle exec jekyll serve
명령을 통해 서버를 가동하면 끝이다.
보통 Jekyll 블로그를 운영하는 사람들은 Github Pages를 통한 무료 호스팅을 이용하지만, 이미 도메인도 가지고 있고, k8s 클러스터도 운영 중인 나로서는 셀프 호스팅이 하고 싶었다. 여기서는 그 과정을 서술한다.
시작 전 준비 사항
- Kubernetes Cluster
- Container Registry
- 사용 가능한 도메인(production 사용 시)
- Cert-manager
- Ingress Controller
- Markdown 에디터:
Vim
,Obsidian
,VS Code
등
배포 과정
블로그 테마에 대한 설정 방법은 좋은 자료들이 많기에 건너뛰고, 쿠버네티스에 서비스를 배포하려면 어떤 것들이 필요한지, CI/CD와 관련 된 내용만 서술한다.
테마 포크
먼저, 나는 Chirpy Jekyll Theme테마를 사용했음을 밝힌다. 각 테마 별 세부 설정 방식은 달라질 수 있으나, Jekyll의 기본적인 구동 방식은 동일하기 때문에 원하는 테마를 선택하고 해당 레포지토리를 Fork 하여 사용할 수 있도록 한다.
사이트 설정
테마를 포크한 다음 레포지토리를 가져온 후 가장 먼저 할 것은 사이트 설정이다. Jekyll의 기본 구조에 따라 /_config.yml
파일을 열어 자신의 사이트에 맞게 수정한다.
Dockerfile 작성
쿠버네티스 클러스터에 배포하기 위해서는 컨테이너 환경을 만들어야 한다. 컨테이너는 Dockerfile 을 작성하고 빌드하여 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM ruby:3.3
ENV JEKYLL_ENV=production # 빌드 시 임의의 환경변수 값을 사용
# 작업 디렉토리 설정
WORKDIR /apps
COPY . .
# Gemfile을 기반으로 의존성 설치
RUN bundle install \
&& jekyll build
# Jekyll 기본포트 노출
EXPOSE 4000
# Jekyll 서버 실행
CMD ["jekyll", "serve", "--host", "0.0.0.0"]
쿠버네티스 매니페스트 작성
쿠버네티스에 애플리케이션을 배포하기 위해 필요한 최소한의 매니페스트를 작성한다:
- Deployment: GitHub Actions를 통한 배포 자동화 시 변경된 부분만 업데이트(rolling update) 하기 위해 Deployment 오브젝트를 사용
- Service: 배포한 Pod를 네트워크로 연결
- Ingress: 서비스를 외부로 노출
deployment.yaml
작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1
kind: Deployment
metadata:
name: jekyll-blog
namespace: <NAMESPACE>
spec:
replicas: 1
selector:
matchLabels:
app: jekyll-blog
template:
metadata:
labels:
app: jekyll-blog
spec:
containers:
- name: <CONTAINER_NAME>
image:
imagePullPolicy: Always
ports:
- containerPort: 4000
imagePullSecrets:
- name: <DOCKER_SECRET_NAME>
해당 네임스페이스에 docker-secret 등록하는 걸 잊지말자.
service.yaml
작성
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: jekyll-blog
namespace: <NAMESPACE>
spec:
selector:
app: jekyll-blog
ports:
- protocol: TCP
port: 80
targetPort: 4000
ingress.yaml
작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: <ISSUER_NAME>
name: jekyll-blog
namespace: <NAMESPACE>
spec:
ingressClassName: <INGRESS_CONTROLLER>
rules:
- host: <SITE_URL>
http:
paths:
- backend:
service:
name: jekyll-blog
port:
number: 80
path: /
pathType: Prefix
tls:
- hosts:
- <SITE_URL>
secretName: <TLS_NAME>
CI/CD with GitHub Action
포스트 작성 후 페이지를 업데이트 하려면 컨테이너 이미지 빌드 -> 레지스트리에 이미지 업로드 -> 컨테이너 배포 라는 3가지 단계가 필요하다. 이걸 매번 수동으로 하기엔 너무나 귀찮은 작업이다. Github Action을 활용하기 위해선 action runner, workflow 파일을 등록해야 한다.
Action Runner
Github Action을 사용하려면 Action Runner가 필요하다. Github에서 무료로 제공하는 ubuntu-latest, windows-latest, macos-latest가 있고, self-hosted runner를 등록하여 내 서버에서 구동하도록 하는 것도 가능하다.
self-hosted runner 배포 방법은 GitHub Docs를 참고하자.
Repository secrets & variables
변수, 계정 등을 레포지토리 시크릿이나 변수로 등록하면 workflow 작성 시 해당 변수를 불러와 사용할 수 있다.
자세한 방법은 변수에 정보 저장 다큐먼트를 참조한다.
Workflow 작성
컨테이너 빌드 및 배포를 위한 워크플로우 파일을 작성한다:
- 이미지 빌드/푸시: Github에서 제공하는 ubuntu-latest 러너 사용
- 배포: arc-runner-set을 통한 로컬 쿠버네티스 클러스터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
name: Deploy Jekyll Blog on new Post
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:${{ github.sha }}
outputs:
image_tag: ${{ github.sha }}
deploy:
needs: build-and-push
runs-on: arc-runner-set
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install kubectl
uses: azure/setup-kubectl@v3
with:
version: "v1.28.2" # 쿠버네티스 버전과 맞춘다. 참고) https://kubernetes.io/releases/version-skew-policy/#kubectl
- name: Set up kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG_DATA }}" | base64 --decode > $HOME/.kube/config
- name: Update Kubernetes manifest image tag
run: |
sed -i "s|image: .*$|image: ${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:${{ needs.build-and-push.outputs.image_tag }}|" manifests/deployment.yaml
- name: Deploy to Kubernetes
run: |
kubectl apply -f manifests/*
- name: Check deployment status
run: kubectl -n apps rollout status deployment/jekyll-blog
배포
모든 준비가 끝났다면 git commit
을 통해 Github로 코드를 업로드 한다.
1
2
git add .
git commit -m "this is the 1st commit!"
Github 레포지토리의 Actions 메뉴로 가서 CI/CD가 잘 동작하는지 확인해보자.
사이트 URL로 접속해 블로그가 정상적으로 배포되었는지 확인하자. 이때, deployment의 로그를 확인하면 발생하는 문제를 파악하는데 도움이 된다.
1
kubectl -n <NAMESPACE> logs -f deployment/jekyll-blog
마무리 및 팁
운영 시 유용한 팁 하나.
Git branch를 main과 draft로 구분하면 초안/공개본 관리가 수월하다.