
1. Introduction
📌 Motivation
경량 서버를 만들긴 했는데, 막상 요놈을 어디 띄워야 할 지 고민이 되었다.
고작 이거 하나 올리겠다고 클라우드를 쓴다?
내 목표는 최소 비용으로 최대 광고 수익을 이끌어내는 것인 만큼, 이젠 그 비용마저도 아깝다는 생각이 들었다.
그러다가 옆을 슥 보니, swift 개발 손 땐 이후로 또 다시 방치되고 있는 내 맥북이 보였다.

주인된 도리로써, 어찌 내 PC가 100% 효율로 일하지 않는 상황을 방관할 수 있겠는가.
내가 그리 만들어 주겠다.
그리하여, 미루고 미루던 로컬 홈 서버 개장 프로젝트가 시작되었다.
📌 GoCD
서버를 로컬 PC에 띄우는 거야 그렇다 쳐도, 문제는 CD 파이프라인을 어떻게 해야 할 지가 고민이었다.
차라리 aws가 더 쉽지, github actions를 로컬 PC랑 ssh 연결 설정하고, 이것저것 작업해주려니 못할 건 없지만 벌써부터 노잼 시작이었다.
그런데 나는 개발자다.
그 말은 즉, 나같은 생각을 하는 개발자가 더 없을 리가 없다는 말이다.
그래서 구글을 이잡듯이 뒤져봤더니, 아니나 다를까 재밌어 보이는 CD 관리를 도와주는 오픈소스가 존재하더라.
GitHub - gocd/gocd: GoCD - Continuous Delivery server main repository
GoCD - Continuous Delivery server main repository. Contribute to gocd/gocd development by creating an account on GitHub.
github.com
Open Source Continuous Delivery and Release Automation Server | GoCD
End to End Visualization GoCD’s value stream map shows your entire path to production in a single view. Easily navigate across jobs, spot inefficiencies, and optimize your process. No plugin required, out of box CD.
www.gocd.org

솔직히 뭔 소린지 모르겠다만, CD 파이프라인이 어차피 거기서 거기겠지 뭐.
이게 매력이 있는 점은 self-hosted에 오픈소스라는 이점, 즉 무료라는 것이다.
커스텀해야 할 건 좀 있지만, 앞으로 쏟아낼 프로토타입 서비스들은 검증이 되기 전까진 모두 홈 서버로 올려버릴 예정이므로 변동이 많을 워크 플로우 구성에 썩 괜찮아 보였다.
🟡 GoCD에서 중요한 개념 14가지
- Task
- Job
- Stage
- Pipeline
- Materials
- Pipeline Dependencies
- Fan-in and fan-out
- Value Stream Map
- Artifacts
- Agent
- Resources
- Environment
- Envirionment Variables
- Templates
용어를 보면 알겠지만, 딱히 새로운 게 없다.
그냥 CI/CD 파이프라인 자주 만져봤으면, 얼추 이해가 될 것이다.
🟡 Server & Agent
- GoCD는 Server와 Agent로 나뉘어져 있는데, 관리자와 실무자 같은 관계라고 보면 된다.
- Server
- 파이프라인의 설정, 스케줄링, 웹 UI 담당하는 중앙 관리 시스템
- 전체 CI/CD 과정을 조율하고 모니터링
- 파이프라인 구성 정보, 이력, 아티팩트 저장
- 웹 인터페이스 제공 (대시보드, 관리자 페이지 등)
- Agent
- Server는 플로우 감시만으로도 힘들기 때문에 실제 일(Job)은 Agent에게 짬때린다.
- 빌드, 테스트, 패키징, 배포 등의 작업을 수행하고, 작업 완료 시 서버에게 결과를 보고한다.
- 여러 에이전트를 병렬로 동시 운영이 가능하다.
예시를 하나 들자면, 서버가 github 변경 사항 감지해서 파이프라인 설계도를 agent에게 주면, 실제 파이프라인 따라 일을 하는 게 agent가 된다.
서버는 관리자기 때문에 세부적인 업무를 직접 수행하지 않는다.
📌 Architecture

내가 생각하는 구조는 위와 같다.
갑자기 polling은 어디서 튀어나온 건가 싶겠지만, GoCD 공식 문서에서 Material(여기선 github)에 대한 주기적인 polling으로 변경 감지를 수행한다고 한다.
디테일하게 알아보진 않아서, 그냥 그런가보다 하고 넘어갔다.
테스트와 배포 이후 서버 측으로 실행하는 스크립트가 있는데, 오늘의 목표는 저기까진 아니고
- GoCD 서버 띄우고 파이프라인 구축 및 github 연동
- GoCD Agent로 빌드 서버 구축 후 docker container 실행
딱, 이 두 가지만 수행할 예정.
📌 References
토스ㅣSLASH 23 - 유연하고 안전하게 배포 Pipeline 운영하기 - 3 (GoCD를 이용한 파이프 라인 구축 개념
토스ㅣSLASH 23 - 유연하고 안전하게 배포 Pipeline 운영하기 - 1 (기술 정리) 토스ㅣSLASH 23 - 유연하고 안전하게 배포 Pipeline 운영하기 - 1 (기술 정리) 토스 Slash 23에서 나오는 운영 방법 영상 속 개념,
crongb.tistory.com
GoCD Pipeline 구축
GoCD는 이름에서 알 수 있듯이 CD, 지속적인 배포를 관리하는 소프트웨어다. 오픈소스이며 여러 서버를 지원한다. 필자는 맥 미니를 홈서버로 운용중인데, 맥 ARM 환경에서도 동작한다. 서비스가
velog.io
위 두 가지 블로그를 가장 많이 참고했고, 도움이 되어서 올려놓고 시작.
나머지는 전부 공식 문서 보고 해결했다.
2. GoCD Server
📌 Docker Hub
https://hub.docker.com/r/gocd/gocd-server/
hub.docker.com
gocd-server를 로컬에 직접 설치해도 되지만, gocd에서도 이젠 docker로 받는 걸 권장한다는 문구를 본 것 같다. (캡쳐해둔 줄 알았는데 안 해놔서 팩트인지는 모르겠다.)
추후 gocd agent가 동적으로 할당되도록 구성하기 위해서라도 docker를 쓰지 않을 이유가 없긴 하다.
docker run -d --name gocd-server -p 8153:8153 -v ./godata:/godata -v ./home/go:/home/go gocd/gocd-server:v25.1.0

공식 문서에 친절하게 나와있는 명령어를 그대로 사용했다.
- /godata: GoCD 서버의 모든 configuration, logs 그리고 perform builds가 해당 디렉토리에 저장된다. 만약 이 설정들을 로컬에서 편하게 관리하고 싶으면 마운트하자.
- /home/go: SSH 같은 보안 인증 설정을 추가하고 싶으면 마운트하면 된다.

8153 포트로 접속하면 Web UI에 접근할 수 있다.
참고로 나는 현재 8153 포트만 개방해놨는데, 해외 포럼을 보면 8154 포트를 개방하고 https로 외부 접속이 가능하도록 만드는 경우도 봤었다.
당장은 필요없는 설정이라 패스.
이제 파이프라인을 구축해보자.
💡 기초 용어 정리
제일 서두에 GoCD에서 중요한 키워드 14가지를 적어놨으나, 아는 사람은 알아서 안 읽었을 거고, 모르는 사람은 몰라서 안 읽었을 것이다.
그래서 현재 진행하는 파트에서 중요한 것만 짧게 정리해놓았다.
• Material: 소스 코드가 있는 곳 (ex: github remote repository)
• Pipeline: 전체 배포 프로세스
• Stage: 파이프라인 내의 독립적인 단계 (ex: 빌드, 테스트, 배포)
• Job: 스테이지 내에서 실행되는 작업의 단위
• Task: 작업 내에서 실행되는 개별 명령어 (실제 실행되는 스크립트가 나옴)
📌 Material

Web UI에 워낙 그림과 함께 설명이 친절해서 적을 게 없긴 한데, Material은 소스 코드가 있는 위치라고 보면 된다.
변경 사항을 추적할 github repository 링크를 복붙하면 되는데, master 브랜치를 찾을 수 없단다.
침착하게 Advanced Settings를 열어보자.

Repository Branch에 main을 적어주고 다시 Test Connection을 누르면 성공한다.
우리는 인종차별 주의자가 아니기 때문에, master 브랜치로 통과가 된다면 main 브랜치로 변경하는 것을 염두해보자.
사진에는 나오지 않지만 몇 가지 옵션이 더 있다.
- Alternate checkout path
- GoCD Agent가 Material 체크아웃할 때 사용할 경로를 지정 (github action처럼 소스 코드를 일단 서버로 받아오면서 진행되기 때문)
- 여러 파이프라인이 동일 소스 코드 또는 데이터에 대한 다른 작업을 수행해야 할 때 필요하다.
- 사용하지 않으면, 기본적으로 Agent의 sandbox 디렉토리 하위에 체크아웃을 수행한다.
- Material name
- Material 식별값 (정의하지 않으면 임의 값 생성)
- 같은 저장소 내 여러 브랜치, 혹은 여러 Material이 사용되는 경우 편리
- Repository polling behavior
- GoCD Server가 Material의 변경 사항 감지 주기 설정
- 저장소 변화가 잦고, 빠르게 반영되어야 할 수록 주기를 짧게 잡으면 되나, 그만큼 서버 리소스가 증가한다.
개인적으로 이런 디테일한 커스텀 항목은 나중에 필요할 때 정하는 게 가장 낫다고 생각해서 현재는 기본값을 넣었다.
📌 Pipeline

- Pipeline Name
- 파이프라인 식별값. (따라서 GoCD 내에서 고유해야 함)
- 해당 이름으로 특정 파이프라인 참조 및 관리 가능.
- Pipeline Group
- 여러 파이프라인을 논리적으로 그룹화할 때 필요
- ex: 같은 팀, 같은 프로젝트, 같은 작업을 수행하는 파이프라인을 grouping하여 관리할 때 Group을 지정
- 안 해봐서 모르겠으나, 나중에 Group끼리 대시보드에서 보기 쉽게 배열하거나 필터링할 수 있다고 함.
- Parameters
- 파이프라인 내에서 사용되는 변수 정의
📌 Stage Details

이건 뭐 Stage Name말고 쓸 게 없다.
참고로 'Stage, Job, Task는 여러 개 등록할 수 있어야 하는 거 아닌가?'라는 의문이 들 텐데, 파이프라인 일단 생성하고 나중에 편집 페이지 들어가면 추가할 수 있다.
📌 Job and Tasks

- 스크립트 창이 보일 텐데, 그 부분이 Job에 등록될 Task를 의미한다.
- Plain Text Variables
- 텍스트 형식의 변수 지정 (공개적으로 알려줘도 상관없는 데이터만 사용할 것!)
- 파이프라인 작업 내에서 직접 참조 가능
- Secure Variables
- 데이터베이스 암호, API 키와 같은 민감한 정보를 변수로 지정
- 파이프라인 작업에서 사용될 때, 자동으로 복호화되어 사용됨.
이건 뭐 github actions로 aws 배포 한 번만 해봤으면 어렵진 않은데, 이 때만해도 Job 등록 여러 개는 어떻게 등록하는 건가 싶어서 task에 uvicorn 명령어만 써놨었다. (당연히 실패할 걸 알고 있었음)
이러고 파이프라인 생성을 하면, 아래와 같이 대시보드에 추가된다.


문제는 작업을 수행해줄 Agent가 없어서 요원을 애타게 기다리고 있다.

우리들의 Agent를 급파해주러 가자.
3. GoCD Agent
📌 Docker Image
대표적으로 3가지 있다.
더 있긴 한데, 나도 아직 전부 파악하질 못 해서 ㅎㅎ;
1️⃣ gocd/gocd-agent-alpine-3.19 (github)
- OS: Linux
- 매우 가볍다는 장점이 있으나, MacOS와 CPU 아키텍처 호환이 안 됨. (Linux는 amd64 기반, MacOS는 arm64 기반)
- 실제로 이거 때문에 Windows, MacOS 모두 동작하던 Dockerfile이 rosetta 에런지 뭔가 하는 걸로 곤욕을 치뤘다.
2️⃣ gocd/gocd-agent-docker-dind (github)
- OS: (확인을 못 했는데 아마 Linux일 듯..)
- docker in docker라는 개념으로, docker daemon을 가지고 있는 container (그래서 실행할 때도 --previleged 플래그 필수)
- 외부로부터 완전히 격리된 환경을 구성할 수는 있으나, 그 만큼 무겁고, 마찬가지로 MacOS와 CPU 아키텍처 호환이 안 됨
3️⃣ gocd/gocd-agent-ubuntu-${version} (github)
- OS: Ubuntu
- 가볍고 MacOS랑 호환도 잘 됨.
처음에 dind를 잘못 이해해서, '어차피 도커 명령어 쓰려면 도커 설치해야 되는데, 당연히 저거 써야 하는 거 아닌가?'라고 생각했었다.
그런데 agent 연결하면서 생각해보니, dind 컨테이너를 써버리면 material의 코드를 빌드하고 컨테이너가 agent 내부에서 동작하게 된다.
적어도 내가 원하는 건 host 서버에서 동작하길 바라기 때문에, gocd-agent와 host의 docker.socket을 마운트해서 연결해줄 필요성이 존재한다.

docker run -d \
--name langquence-build-server \
-e GO_SERVER_URL=http://$(docker inspect --format='{{(index (index .NetworkSettings.IPAddress))}}' gocd-server):8153/go \
-v ./godata:/godata \
-v ./home/go:/home/go \
-v /var/run/docker.sock:/var/run/docker.sock \
gocd/gocd-agent-ubuntu-22.04:v25.1.0
처음엔 실행 중인 go_server랑 연결하고 docker.sock을 mount 해주었으나, 당연히 agent 내에서 docker 명령어 자체를 이해 못하고 있었다.
그럼 docker.io를 apt로 받아와야 하는데, 문제는 gocd-server, gocd-agent 모두 기본 사용자가 root가 아니라 go로 정해져 있다는 것이다.
docker run -d \
--name langquence-build-server \
-e GO_SERVER_URL=http://$(docker inspect --format='{{(index (index .NetworkSettings.IPAddress))}}' gocd-server):8153/go \
-v ./godata:/godata \
-v ./home/go:/home/go \
-v /var/run/docker.sock:/var/run/docker.sock \
gocd/gocd-agent-ubuntu-22.04:v25.1.0 \
bash -c "apt update && apt install -y sudo && sudo apt install -y docker.io && /docker-entrypoint.sh"
go 사용자 권한이 최소로 맞춰져있어서 그런가, 위 명령어도 실패한다.
그래서 그냥 Dockerfile을 하나 만들어줬다.
FROM gocd/gocd-agent-ubuntu-22.04:v25.1.0
USER root
RUN apt update && apt install -y docker.io
USER go
root로 docker.io만 잽싸게 설치해주고, 다시 go 사용자로 전환하는 초간단 스크립트
docker build -t gocd-agent-with-docker .
docker run -d \
--name langquence-build-server \
-e GO_SERVER_URL=http://$(docker inspect --format='{{(index (index .NetworkSettings.IPAddress))}}' gocd-server):8153/go \
-v ./godata:/godata \
-v ./home/go:/home/go \
-v /var/run/docker.sock:/var/run/docker.sock \
gocd-agent-with-docker
이걸 빌드하고 실행해주면, agent에서 docker 명령어도 잘 인식함과 동시에 host의 docker를 사용하게 된다.


잘 실행되었다면 Web UI > AGENTS에서 agent의 container id로 등록이 된 것을 볼 수 있다.
처음에 Status가 Pending 상태에서 바뀌지 않는 경우가 있는데, Agent 선택하고 Enable 클릭하면 Idle 상태로 전환된다.
📌 Task


예상했던 대로 task가 실패했다.
이제 server에서 pipeline을 수정해주러 가자.

처음에 뭣도 모르고 command를 위와 같이 입력했더니 계속 실패했다는 문구가 떴다.
권한 문제, 경로 문제, 환경 변수 문제 중 하나일 것이라 생각해서, 진짜 별의 별 짓을 다해도 계속 에러가 났었는데
다른 사람이 한 걸 보니까, arguments를 잘못 전달하고 있는 거였다. ㅎㅎ

이렇게 입력하면 끝나는 문제였다...^^
놀랍게도 위 두 사진 사이에는 4시간이라는 공백이 존재한다.
4. Conclustion
📌 Success but...


겨우겨우 CD 파이프라인이 정상 동작하는 것을 확인을 하긴 했으나, 그냥 github에서 코드 가져와서 dockerfile 실행한 정도라 했다고 하기도 우습다.
앞으로 개선해나갈 점들이 많은데,
- 무중단 배포 전략
- 트래픽에 따른 오버 스케일링 전략
- 배포, 개발 서버 분리 전략
- 여러 독립된 서비스 분리 전략
- GoCD를 docker-compose로 마이그레이션
- VPN 설정
- 사설 Network망에 Agent 들을 미리 띄워놓고, 빌드 서버 작업 완료 후 Agent들이 반영하는 구조로 개선
등등
해보고 싶은 게 너무 많긴 한데, 일단 당장 급한 사항은 따로 있기도 하고 바보짓한다고 시간을 너무 날려먹은 관계로 추후 다시 개선할 예정.
이상 끝.