[CI/CD Study 3주차] Jenkins, Gogs 를 이용한 CI/CD 배포
- -
CoudNet@ 팀의 가시다님께서 리딩하시는 CI/CD Study 3주차 스터디 내용 정리
이번 주차에서는 Jenkins, Gogs, ArgoCD 를 통한 CI/CD 에 대해서 학습했습니다.
0. 실습 환경 구성
이번 주차의 실습은 Kind 를 통해 2개의 노드(컨트롤 플레인, 워커 노드)로 구성하였으며,
실습에 필요한 Jenkins 와 Gogs 는 Docker 로 구성했습니다.
0.1. Kind 배포
# Kind 클러스터 배포
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "0.0.0.0"
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
- role: worker
EOF
0.2. Jenkins, Gogs 배포
# Docker Compose 파일 생성
cat <<EOT > docker-compose.yaml
services:
jenkins:
container_name: jenkins
image: jenkins/jenkins
restart: unless-stopped
networks:
- cicd-network
ports:
- "8080:8080"
- "50000:50000" # Jenkins Agent - Controller : JNLP
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./jenkins_home:/var/jenkins_home # (방안1) 권한등으로 실패 시 ./ 제거하여 도커 볼륨으로 사용 (방안2)
gogs:
container_name: gogs
image: gogs/gogs
restart: unless-stopped
networks:
- cicd-network
ports:
- "10022:22" # Git Clinet - Git SSH Service : push, pull, clone
- "3000:3000"
volumes:
- ./gogs-data:/data # (방안1) 권한등으로 실패 시 ./ 제거하여 도커 볼륨으로 사용 (방안2)
volumes:
jenkins_home:
gogs-data:
networks:
cicd-network:
driver: bridge
EOT
# Docker 실행
docker compose up -d
Jenkins 초기 비밀번호 확인
Jenkins 를 생성했기 때문에, 초기 비밀번호 로그인을 통해 Jenkins 설정이 필요합니다.
# 초기 비밀번호 확인
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# Jenkins 오픈
open "http://127.0.0.1:8080"
비밀 번호 입력 후 Suggested Plugin 을 설치합니다.

그 후 로그인할 어드민 계정을 설정해줍니다.
실습인 관계로 admin / admin123 으로 설정했습니다.

그리고 IP 는 본인 노트북이 사용하고 있는 사설 IP 로 설정합니다.
저는 192.168.200.121 를 사용하고 있습니다.
ifconfig | grep 192


0.3. Jenkins 컨테이너에 Docker 설치
Jenkins 에서 컨테이너 빌드를 위해 Docker 를 설치해줍니다.
Jenkins 컨테이너 실행 시 Docker 소켓을 바인딩했으므로
Docker CLI 를 설치하면 Host(내 PC)의 Docker Server 와 통신할 수 있는 상태가 됩니다. (Docker out of Docker)
# Jenkins 컨테이너 진입
docker compose exec --privileged -u root jenkins bash
# Docker 설치
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update && apt install docker-ce-cli curl tree jq yq wget -y
# Docker cli 실행
docker info
docker ps
Jenkins 컨테이너에서 docker ps 를 실행했음에도 컨테이너 밖의 PC 에서 실행되고 있는 컨테이너가 전부 보이는 것을 알 수 있습니다.

Jenkins 컨테이너에서 root 가 아닌 사용자도 docker 를 실행할 수 있도록 권한 설정
# 권한 설정
groupadd -g 2000 -f docker
chgrp docker /var/run/docker.sock
ls -l /var/run/docker.sock
usermod -aG docker jenkins
cat /etc/group | grep docker
exit
# 변경된 권한 적용을 위해 컨테이너 재기동
docker compose restart jenkins
# 권한 확인
docker compose exec jenkins docker ps
0.4. Gogs 설정
Gogs 는 경량화된 Git 서비스를 셀프 호스팅할 수 있는 도구입니다.
여기서는 Github 나 Gitlab 대신에 간단하게 사용할 수 있는 Gogs 를 사용했습니다.
이미 Docker Compose 로 함께 배포가 되었기 때문에 Gogs 설정을 진행합니다.
# Gogs 오픈
open "http://127.0.0.1:3000/install"
변경할 부분은 데이터 베이스 유형, 애플리케이션 URL, 관리자 계정 등입니다.
- 데이터베이스 : SQLite3

- 애플리케이션 URL : 본인 PC 사설 IP 주소
- 기본 브랜치 : main

- 관리자 계정 ID : devops

이후 초기 설정이 완료가 되면 로그인하고 사용자 토큰을 발급받습니다.


그리고 CICD 실습을 위해 2개의 Git Repository 를 생성합니다.


0.5. Gogs 저장소 설정
로컬 PC 에서 설정된 Gogs 저장소를 클론한 후 작업을 해줍니다.
# 변수 설정
# TOKEN: Gogs 에서 발급받았던 사용자 토큰 입력
# MyIP: PC 사설 IP
TOKEN=e3744bb0fdf48783c8c1c166dd7b5e13090112f2
MyIP=192.168.200.121
# dev-app 레포지토리 클론
git clone http://devops:$TOKEN@$MyIP:3000/devops/dev-app.git
cd dev-app
# 로컬 디렉토리 Git Config 설정
git config --local user.name "devops"
git config --local user.email "a@a.com"
git config --local init.defaultBranch main
git config --local credential.helper store
git --no-pager config --local --list
cat .git/config
Push 할 파일 생성
cat > server.py <<EOF
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from datetime import datetime
import socket
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
match self.path:
case '/':
now = datetime.now()
hostname = socket.gethostname()
response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.1\n")
response_string += f"Server hostname: {hostname}\n"
self.respond_with(200, response_string)
case '/healthz':
self.respond_with(200, "Healthy")
case _:
self.respond_with(404, "Not Found")
def respond_with(self, status_code: int, content: str) -> None:
self.send_response(status_code)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(bytes(content, "utf-8"))
def startServer():
try:
server = ThreadingHTTPServer(('', 80), RequestHandler)
print("Listening on " + ":".join(map(str, server.server_address)))
server.serve_forever()
except KeyboardInterrupt:
server.shutdown()
if __name__== "__main__":
startServer()
EOF
# Dockerfile 생성
cat > Dockerfile <<EOF
FROM python:3.12
ENV PYTHONUNBUFFERED 1
COPY . /app
WORKDIR /app
CMD python3 server.py
EOF
# Version 생성
echo "0.0.1" > VERSION
# Gogs 저장소 Push
git add .
git commit -m "Add dev-app"
git push -u origin main
업로드가 잘 된것을 확인했습니다.

0.6. 도커 허브 설정
빌드된 Docker 이미지를 저장하기 위해 Dockerhub 에서 토큰을 발급한 후,
Private Repository 를 하나 생성해줍니다.
- 저장소 명 : dev-app

1. Jenkins Pipeline 실행
이제 모든 실습 환경 구성이 끝났으니 Jenkins 부터 실행해보겠습니다.
Jenkins 는 전통적인 CI/CD 배포 도구로 수많은 플러그인 덕분에 다양한 배포 과정에서 사용되는 강력한 오픈소스입니다.
1.1. Jenkins Plugin 설치
실습에 사용되는 플러그인만 3개 설치해서 세팅하겠습니다.
이 중 Gogs 플러그인은 더이상 유지보수 되지 않아 심각한 보안 이슈가 있는 것으로 보이기 때문에,
이번 실습 외에는 절대 사용하면 안됩니다.
1. Pipeline: Stage View
2. Docker Pipeline
3. Gogs

1.2. Jenkins 자격증명 설정
Jenkins 파이프라인 실행을 위해 Credentials 에서 Gogs 와 Dockerhub 자격증명을 설정합니다.
Gogs-crd
- Kind : Username with password
- Username : devops
- Password : <Gogs 토큰>
- ID : gogs-crd
dockerhub-crd
- Kind : Username with password
- Username : <도커 계정명>
- Password : <도커 계정 암호 혹은 토큰>
- ID : dockerhub-crd

1.3. Jenkins Pipeline 생성
초기 파이프라인을 생성해서 잘 동작하는 지 확인합니다.
Gogs 저장소에 Push 된 코드를 빌드하고 Dockerhub 에 이미지를 업로드하는 파이프라인입니다.
아래 파이프라인 스크립트를 설치한 Jenkins 에 넣어줍니다.
pipeline {
agent any
environment {
DOCKER_IMAGE = 'kimalarm2/dev-app' // Dockerhub 저장소 명칭
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.200.121:3000/devops/dev-app.git', // 로컬 Gogs 저장소 주소
credentialsId: 'gogs-crd' // Gogs Credentials ID
}
}
stage('Read VERSION') {
steps {
script {
// VERSION 파일 읽기
def version = readFile('VERSION').trim()
echo "Version found: ${version}"
// 환경 변수 설정
env.DOCKER_TAG = version
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
// DOCKER_TAG 사용
def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
appImage.push()
appImage.push("latest")
}
}
}
}
}
post {
success {
echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
}
failure {
echo "Pipeline failed. Please check the logs."
}
}
}

파이프라인이 성공적으로 실행되었고, 이미지도 잘 업로드 되었습니다.


1.4. Kind 에 이미지 배포
Jenkins 로 빌드한 이미지를 Kind 에 배포해보겠습니다.
# Dockerhub 계정 변수 설정
DHUSER=kimalarm2
DHPASS=<도커허브 토큰 입력>
# Secret 배포 (Dockerhub 자격증명)
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=$DHUSER \
--docker-password=$DHPASS
# Deployment 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
httpGet:
path: /healthz
port: 80
scheme: HTTP
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
imagePullSecrets:
- name: dockerhub-secret
EOF
# Service 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: timeserver
spec:
selector:
pod: timeserver-pod
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 30000
type: NodePort
EOF

1.5. 애플리케이션 업데이트
Gogs 의 코드를 업데이트 한 후, Jenkins 에서 새로 빌드하고 이것을 Kind 에 배포해보겠습니다.
Gogs 저장소에서 VERSION 파일과 Server.py 의 버전을 0.0.1 에서 0.0.2 로 변경 후 푸시합니다.

git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
이후 Jenkins Pipeline 에서 새로 빌드하고 Kind Deployment 를 업데이트 합니다.
kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.2 && watch -d "kubectl get deploy,ep timeserver -owide; echo; kubectl get rs,pod"

2. Gogs - Jenkins Trigger
지금까지는 코드를 업데이트한 후 수동으로 Jenkins 를 빌드했습니다.
Gogs 코드가 업데이트되면 바로 Jenkins 에서 빌드를 수행하도록 웹훅을 설정해보겠습니다.
2.1. Gogs 웹훅 생성
Gogs 와 Jenkins 가 동일한 PC 에서 컨테이너로 가동되고 있기 때문에 내부 설정이 필요합니다.
## gogs 내부 컨테이너에서 아래 설정 파일에 NETWORK 허용 추가
# 설정 파일 위치
cd /data/gogs/conf
vi app.ini
[security]
INSTALL_LOCK = true
SECRET_KEY = uZRj9neBbhbiDfC
LOCAL_NETWORK_ALLOWLIST = 192.168.200.121 ## 로컬 PC 사설 IP 추가
# 설정 적용을 위한 컨테이너 재기동
docker compose restart gogs
이후 Gogs 레포지토리에 가서 웹훅 설정을 추가합니다.

2.2. Jenkins Gogs 웹훅 연동
Jenkins Pipeline 에서 Gogs 웹훅을 연동해줍니다.


Jenkins SCM 설정
이제 Git 소스 코드에 저장된 Jenkinsfile 을 사용할 예정이므로 SCM 설정을 해줍니다.

Jenkinsfile 생성
Jenkinsfile 을 아래와 같이 생성해주고 VERSION 을 0.0.2 에서 0.0.3 으로 변경한 후 푸시합니다.
pipeline {
agent any
environment {
DOCKER_IMAGE = 'kimalarm2/dev-app' // Dockerhub 저장소 명칭
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.200.121:3000/devops/dev-app.git', // 로컬 Gogs 저장소 주소
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('Read VERSION') {
steps {
script {
// VERSION 파일 읽기
def version = readFile('VERSION').trim()
echo "Version found: ${version}"
// 환경 변수 설정
env.DOCKER_TAG = version
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
// DOCKER_TAG 사용
def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
appImage.push()
appImage.push("latest")
}
}
}
}
}
post {
success {
echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
}
failure {
echo "Pipeline failed. Please check the logs."
}
}
}
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
이후 Jenkins 에서 파이프라인이 동작되고 Dockerhub 에 0.0.3 이미지가 업로드 된 것을 확인하였습니다.


'CICD' 카테고리의 다른 글
| [CI/CD Study 4주차] ArgoCD 사용 & Autopilot (0) | 2025.11.09 |
|---|---|
| [CI/CD Study 3주차] ArgoCD 를 이용한 CI/CD 배포 (0) | 2025.11.02 |
| [CI/CD Study 2주차] Cloud Native CI/CD - Tekton 실습 (0) | 2025.10.26 |
| [CI/CD Study 2주차] Helm Chart 생성 및 배포 (0) | 2025.10.26 |
| [CI/CD Study 1주차] 컨테이너를 빌드하는 다양한 방법 (0) | 2025.10.19 |