Gitea Actions로 홈랩 CI/CD 파이프라인 만들기 - 배포 자동화 직접 구축기
개인 포털 프로젝트를 자체 Gitea에 올려서 쓰고 있는데, 코드를 고칠 때마다 로컬에서 clone → 서버 접속 → podman build → 이미지 tar로 저장 → containerd에 import → kubectl로 이미지 갱신을 손으로 반복하고 있었습니다. 처음엔 "귀찮아도 몇 줄인데 뭐"였는데, 하루에 몇 번씩 반복하다 보니 이건 자동화해야겠다 싶어서 Gitea Actions를 붙여봤습니다. GitHub Actions는 회사에서 써봤지만 자체 호스팅한 Gitea에서 러너(Runner)까지 직접 등록해서 돌리는 건 처음이라 삽질이 좀 있었는데, 그 과정을 정리해봅니다.
Gitea Actions가 뭔가요?
Gitea Actions는 Gitea 1.19부터 내장된 CI/CD 기능입니다. 가장 큰 특징은 워크플로우 문법이 GitHub Actions와 거의 동일하다는 점이에요. .gitea/workflows/ 폴더에 YAML 파일을 넣고, on, jobs, steps 구조를 그대로 씁니다. GitHub Actions 문서나 예제를 찾아서 거의 그대로 옮겨써도 될 정도로 호환성이 좋습니다. 다만 Gitea 서버 자체는 워크플로우를 실행하지 않고, Act Runner라는 별도 에이전트를 서버(또는 다른 머신)에 등록해야 실제 잡(Job)이 돌아갑니다.
왜 GitHub Actions 대신 이걸 쓰나
사내망/홈랩처럼 코드와 배포 대상이 전부 폐쇄망 안에 있는 경우, GitHub Actions를 쓰려면 결국 외부(GitHub)에 코드를 올리거나 self-hosted runner로 다시 내부망을 열어줘야 합니다. 이미 Gitea를 자체 운영 중이라면 추가 서비스를 띄울 필요 없이 러너 하나만 등록하면 되고, 코드와 빌드 결과물이 외부로 한 번도 안 나간다는 점도 마음이 편했습니다. 문법이 GitHub Actions와 거의 같아서 학습 비용도 거의 없었고요.
아키텍처 한눈에 보기
Gitea 서버 - 코드 저장소이자 워크플로우 트리거의 시작점입니다. push, PR 등 이벤트가 발생하면 등록된 러너에게 잡을 전달합니다.
Act Runner - 실제로 워크플로우의 각 step을 실행하는 에이전트입니다. Gitea 서버에 등록(register)한 뒤 계속 폴링하면서 새 잡이 오면 실행합니다.
Workflow(.gitea/workflows/*.yml) - push 같은 이벤트에 어떤 job/step을 실행할지 정의한 파일입니다.
배포 대상 - 이번 예시에서는 러너가 직접 podman으로 이미지를 빌드하고, k3s의 containerd에 import한 뒤 kubectl로 롤아웃까지 진행합니다.
직접 구축해보기
1. Gitea에서 Actions 기능 활성화
Gitea 설정 파일 app.ini에 아래 섹션을 추가하고 Gitea를 재시작합니다.
[actions]
ENABLED = true
재시작 후 저장소 설정 메뉴에 Actions 탭이 새로 보이면 정상 활성화된 겁니다.
2. Act Runner 설치 및 등록
먼저 저장소(또는 조직/인스턴스) 설정 페이지에서 러너 등록용 토큰을 발급받습니다. 이후 러너 바이너리를 내려받아 등록합니다.
wget https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
chmod +x act_runner-linux-amd64
sudo mv act_runner-linux-amd64 /usr/local/bin/act_runner
act_runner register \
--instance http://gitea-내부주소:3000 \
--token 발급받은토큰 \
--name k3s-host-runner
여기서 처음에 걸렸던 부분이 있는데, Act Runner는 기본적으로 각 잡을 도커 컨테이너 안에서 실행하도록 설계돼 있습니다. 그런데 제 서버는 도커 대신 Podman을 쓰고 있어서( docker 소켓 자체가 없음) 기본 설정 그대로는 러너가 잡을 실행하지 못했습니다. config.yaml에서 실행 방식을 컨테이너가 아니라 러너를 등록한 호스트에서 직접 셸로 실행하도록 바꿔야 했습니다.
act_runner generate-config > config.yaml
config.yaml에서 container 관련 설정 대신 아래처럼 호스트 실행 모드를 쓰도록 labels를 host로 지정합니다.
act_runner register \
--instance http://gitea-내부주소:3000 \
--token 발급받은토큰 \
--labels host
워크플로우 쪽에서도 runs-on: host로 맞춰주면, 잡의 각 step이 별도 컨테이너 없이 러너가 떠 있는 서버(=k3s 노드) 위에서 직접 실행됩니다. 덕분에 러너 프로세스가 로컬의 podman, kubectl, k3s의 containerd 소켓을 그대로 쓸 수 있게 됩니다.
sudo act_runner daemon --config config.yaml
재부팅 후에도 계속 떠 있어야 하니, 이 명령도 앞서 다뤘던 방식처럼 systemd 서비스로 등록해두는 걸 추천합니다.
3. 워크플로우 파일 작성
실제로 제가 손으로 반복하던 배포 과정을 그대로 워크플로우로 옮긴 예시입니다. .gitea/workflows/deploy.yml로 저장합니다.
name: Build and Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: host
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build image with podman
run: podman build -t localhost/myapp:${{ gitea.sha }} .
- name: Save and import into k3s containerd
run: |
podman save localhost/myapp:${{ gitea.sha }} -o /tmp/myapp.tar
ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import /tmp/myapp.tar
- name: Rollout new image
run: |
kubectl set image deployment/myapp myapp=localhost/myapp:${{ gitea.sha }} -n myns
kubectl rollout status deployment/myapp -n myns
커밋 SHA를 태그로 쓰면 매번 새 이미지 태그가 생겨서 imagePullPolicy: Never 상태에서도 이전 이미지와 헷갈릴 일이 없고, 롤백하고 싶을 때 어떤 커밋의 이미지인지 바로 추적할 수 있어 좋았습니다.
4. kubeconfig 같은 민감정보는 Secrets로
러너가 호스트에서 직접 도는 구조라 로컬 ~/.kube/config를 그대로 쓸 수도 있지만, 여러 대상 클러스터에 배포하거나 러너를 여러 대 두는 상황을 대비해서 저장소 설정의 Secrets 메뉴에 kubeconfig나 레지스트리 인증 정보를 등록해두고 워크플로우에서 ${{ secrets.KUBECONFIG }} 형태로 꺼내 쓰는 구조를 권장합니다. 워크플로우 로그에는 시크릿 값이 자동으로 마스킹돼서 출력됩니다.
5. 동작 확인
main 브랜치에 푸시하면 저장소의 Actions 탭에서 잡이 큐에 올라가고, 등록해둔 러너가 잡아서 실행하는 게 실시간 로그로 보입니다. 여기까지 되면 코드 푸시 한 번으로 빌드부터 롤아웃까지 자동으로 끝나는 구조가 완성됩니다.
실무에 적용할 때 팁
runs-on: host로 러너를 도는 방식은 격리가 없다는 뜻이기도 합니다. 신뢰할 수 있는 저장소, 신뢰할 수 있는 사람만 푸시할 수 있는 환경에서만 이 구조를 쓰는 게 안전합니다. 외부에 열려 있는 저장소라면 컨테이너 격리 모드를 유지하는 걸 추천합니다.리소스가 빠듯한 단일 노드 환경이라면 러너 자체도 서버 자원을 쓴다는 걸 감안해야 합니다. 빌드 중에는 CPU/메모리가 순간적으로 튀니, 다른 서비스에 영향이 없는지 한 번씩
kubectl top node로 확인해보는 걸 권장합니다.- 워크플로우 실패 시 Slack이나 메일로 알림을 받고 싶다면, step 마지막에
if: failure()조건으로 알림용 step을 추가하는 패턴이 GitHub Actions와 동일하게 동작합니다. - 여러 프로젝트에 러너를 공유해서 쓸 계획이라면 처음부터
labels를 프로젝트별로 나눠서 등록해두면 워크플로우에서runs-on을 구분해 쓰기 편합니다.
마무리
솔직히 개인 프로젝트 하나 배포하자고 CI/CD까지 필요한가 싶었는데, 막상 붙여놓고 나니 "푸시하고 잊어버리기"가 가능해져서 훨씬 편해졌습니다. 특히 이미 Gitea를 쓰고 있다면 추가 인프라 없이 러너 하나만 등록하면 되니 진입장벽이 낮은 편이고요. 다만 호스트에서 직접 잡을 실행하는 구조는 편의성과 격리를 맞바꾸는 선택이라, 운영 환경이라면 이 부분은 신중하게 판단하시는 걸 추천합니다. 더 자세한 옵션은 Gitea 공식 문서(docs.gitea.com)의 Actions 섹션을 참고하시면 됩니다.
댓글 0
로그인 후 댓글을 작성할 수 있습니다.