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"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- jenkins_home:/var/jenkins_home
gogs:
container_name: gogs
image: gogs/gogs
restart: unless-stopped
networks:
- cicd-network
ports:
- "10022:22"
- "3000:3000"
volumes:
- gogs-data:/data
volumes:
jenkins_home:
gogs-data:
networks:
cicd-network:
driver: bridge
EOT
컨테이너 배포
docker compose up 명령어를 통해서 두 개의 컨테이너를 실행시키고 정보를 확인해보자.
docker compose up -d
docker compose ps
for i in gogs jenkins ; do echo ">> container : $i <<"; docker compose exec $i sh -c "whoami && pwd"; echo; done
Gogs 컨테이너 초기 설정
먼저 초기 설정 페이지에 접속한다.
http://127.0.0.1:3000/install
해당 설정에서는 SQLite를 통해서 했으나 본인이 관리하는 DB로 맞춰서 설정하면 된다. 또한 도메인도 사용하는 도메인이 있다면 해당 도메인을 통해서 사용하면 된다.
추가설정에는 이메일이나 관리자를 설정하는 옵션이 있다. 관리자 설정까지 진행해준다.
바로 로그인해주면 우리만의 private git repository가 생성되게 된다.
이제 로그인 후에 Setting에서 Application에서 새 토큰을 생성버튼을 눌러준다.
토큰 이름은 devops로 하였고, 해당 토큰을 잘 복사해둔다.
24bbb920ed416902053b03de0c5ae0a963fb5363
리포지토리 생성
이제 실제 리포지토리를 생성해보자. 일반적인 회사에 맞게 개발팀용과 데브옵스팀용으로 나눠 진행하고자한다.
다시 대시보드로 돌아와 +버튼을 누르면 리포지토리 생성이 가능하다.
개발팀용 리포지토리
dev-app 라는 이름의 프라이빗 리포지토리로 생성했다.
README 설정 등 사실 github 등 리포지토리를 생성해본 사람들이라면 설정자체가 비슷해서 크게 어려운 것은 없다.
근데 이거 보다보니 한국어 번역이 이상하던데, 가시성 항목의 원문은 다음과 같다. 한국어를 사용하는 사람들은 반드시 확인하기 바란다.
Private -> 비공개
Unlisted -> 리스트 자체가 표시되지 않음
저장소를 만들면 github랑 완전 일치하는 모습이 나온다. 주소도 확인 잘 해놓기 바란다.
동일한 방식으로 데브옵스팀 용으로도 만들었다.
Jenkins 컨테이너 초기 설정
일단 http://127.0.0.1:8080 로 접속해서 초기 화면에 접속한다. 이때 다음 명령어를 통해서 initalAdminPassword를 받아와 입력해준다.
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
56020c6ea65742c693d74147d090b701
본인이 Jenkins를 잘 다루는 사람이라면 Select plugins to install을 통해서 커스텀으로 설치하면 더욱 전문화해서 사용할 수 있을 것이다. 그러나 처음 사용하는 사람이라면 suggested 옵션을 골라서 바로 설치하기 바란다.
설치되는 플러그인과 설치 과정을 보여준다. 시간은 약 5분정도 소요되는 것 같다.
초기 admin 설정
초기 설정을 마친다.
Jenkins config 설정이다. 해당 URL을 통해서 접근하는 것을 허용하게 될 것이다. 마치 ingress 설정과 동일한 절차이기 때문에, 만약 퍼블릭하게 열 생각이 있다면 퍼블릭 IP를 아닐 경우 프라이빗 IP를 넣어주면 된다.
Jenkins 환경설정 - 자격증명 설정
Jenkins 관리 -> Credentials -> Globals -> Add credentials
1. Gogs Repo 자격증명(gogs-crd)
키 | 값 |
kind | Username with password |
Username | |
Password | |
ID |
2. 도커허브 자격증명(dockerhub-crd)
키 | 값 |
kind | Username with password |
Username | |
Password | |
ID |
3. k8s 클러스터 자격증명(k8s-crd)
키 | 값 |
kind | Secret file |
file | kubeconfig 파일 |
ID | k8s-crd |
Jenkins 파이프라인 생성
pipeline {
agent any
environment {
DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://<자신의 집 IP>:3000/devops/dev-app.git', // Git에서 코드 체크아웃
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."
}
}
}
쿠버네티스 디플로이먼트 오브젝트 배포
빌드 후 테스트가 진행가능한 애플리케이션을 배포하겠습니다.
DHUSER=kingdom0220 #dockerhub 계정
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
EOF
ArgoCD
초기 패스워드 확인
Argocd는 초기패스워드를 secret에 저장하여 확인할 수 있는 전략을 취했다. 매우 현명하다. Secret이기 때문에 base64를 통해 디코딩해야한다.
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
실제로 이렇게 확인된 비밀번호를 통해서 접속하면 된다.
admin / dfOGiG4Ax6X9zNME 이런식으로 접속해 보겠다.
가장 먼저 할 일은 User Info에 들어가서 Update Password 버튼을 통해서 비밀번호를 변경하자.
ops-deploy repo 등록
Setting -> Repositories -> Connect repo에 들어가서 git 리포지토리를 등록한다.
해당 리포지토리에서 트리거를 받아서 진행하게 된다.
이 successful이 떠야한다.
일단 ops-deploy에 예시 코드를 작성해보자.
#
cd cicd-labs
TOKEN=24bbb920ed416902053b03de0c5ae0a963fb5363
MyIP=192.168.0.33
git clone http://devops:$TOKEN@$MyIP:3000/devops/ops-deploy.git
cd ops-deploy
#
git --no-pager config --local --list
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
#
git --no-pager branch
git remote -v
#
VERSION=1.26.1
mkdir nginx-chart
mkdir nginx-chart/templates
cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
cat > nginx-chart/templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
index.html: |
{{ .Values.indexHtml | indent 4 }}
EOF
cat > nginx-chart/templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: nginx
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
ports:
- containerPort: 80
volumeMounts:
- name: index-html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: index-html
configMap:
name: {{ .Release.Name }}
EOF
cat > nginx-chart/templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
selector:
app: {{ .Release.Name }}
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
EOF
cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>DEV : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 1
EOF
cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>PRD : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
cat > nginx-chart/Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "$VERSION"
EOF
#
helm template dev-nginx nginx-chart -f nginx-chart/values-dev.yaml
helm template prd-nginx nginx-chart -f nginx-chart/values-prd.yaml
DEVNGINX=$(helm template dev-nginx nginx-chart -f nginx-chart/values-dev.yaml | sed 's/---//g')
PRDNGINX=$(helm template prd-nginx nginx-chart -f nginx-chart/values-prd.yaml | sed 's/---//g')
diff <(echo "$DEVNGINX") <(echo "$PRDNGINX")
#
git add . && git commit -m "Add nginx helm chart" && git push -u origin main
애플리케이션 생성
이제 이 상태로 application에 등록한다.
참고) 애플리케이션 SYNC(동기화) 옵션은 다음과 같다.
- SET DELETION FINALIZER: 애플리케이션이 삭제될 때 관련 Kubernetes 리소스를 함께 삭제하도록 finalizer를 설정한다. 이 옵션이 없으면 Argo CD 애플리케이션만 삭제되고 배포된 리소스는 남아있게 된다.
- SKIP SCHEMA VALIDATION: Kubernetes 리소스 스키마 유효성 검사를 건너뛴다. 사용자 정의 리소스나 새로운 API 버전을 사용할 때 유용한다.
- PRUNE LAST: 동기화 과정에서 더 이상 필요 없는 리소스를 제거(prune)하는 작업을 가장 마지막에 수행한다. 새로운 리소스를 먼저 생성하고 난 후 오래된 리소스를 제거하여 서비스 중단을 최소화할 수 있다.
- RESPECT IGNORE DIFFERENCES: .argocd-allow-hook-resource-modification 주석이 있는 리소스는 Argo CD의 자동 동기화 과정에서 무시한다. 수동으로 수정된 리소스를 보존하고 싶을 때 유용하다.
- AUTO-CREATE NAMESPACE: 클러스터에 네임스페이스가 없을 시 Argo CD에 입력한 이름으로 자동 생성한다.
- APPLY OUT OF SYNC ONLY: 현재 동기화 상태가 아닌 리소스만 배포한다. 불필요한 리소스 적용을 줄여 동기화 성능을 향상시킨다.
- SERVER-SIDE APPLY: 서버 측 적용(server-side apply) 기능을 사용한다. 클라이언트가 아닌 Kubernetes API 서버에서 변경 사항을 처리하여 충돌 해결 및 리소스 관리가 개선된다.
- PRUNE PROPAGATION POLICY: 리소스 제거 시 적용할 전파 정책을 설정한다. 'foreground'는 종속 리소스가 모두 삭제된 후 부모 리소스를 삭제한다.
- Foreground : 부모(소유자, deployment) 자원을 먼저 삭제한다.
- Background : 자식(종속자, pod) 자원을 먼저 삭제한다.
- Orphan : 소유자는 삭제되었으나, 종속자가 삭제되지 않은 경우, 자원을 삭제한다.
- REPLACE: 기존 리소스를 패치(patch)하지 않고 완전히 교체한다. 리소스 상태를 완전히 초기화하고 싶을 때 유용하다.
- RETRY: 동기화 실패 시 자동으로 재시도한다. 일시적인 네트워크 문제나 리소스 충돌이 있을 때 유용하다.
애플리케이션 상세 정보 확인
정보 확인시 outofSync가 발생하고 있다. 현재 git 리포지토리와 실제가 차이가 나는것으로 해당 부분을 확인하기 위해 diff 를 눌러준다
어떤 부분에 차이가 나는지 확인할 수 있다.
이는 CLI를 통해서도 쉽게 알 수 있다.
kubectl get applications -n argocd
kubectl describe applications -n argocd dev-nginx
Sync
SYNC 버튼을 통해서 K8S 운영 환경에 반영해보자.
누르자마자 아주 빠르게 반영되는 것을 확인할 수 있다.
코드 변경 배포
자 우리는 이제 1.26.1에서 1.26.2로 버전이 업그레이드 되도록 진행해보자.
먼저 다음 코드를 모두 복사해서 현재 ops-deploy 리포지토리에 있는 nginx의 버전을 1.26.1에서 1.26.2로 업그레이드 하는 배포를 진행해보자.
#
VERSION=1.26.2
cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>DEV : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>PRD : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
cat > nginx-chart/Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "$VERSION"
EOF
#
git add . && git commit -m "Update nginx version $(cat nginx-chart/VERSION)" && git push -u origin main
버전 1.26.2로 변경하게 되면 다음과 같이 OutOfSync가 나타나게 된다.
기본적으로 3분(180초) 마다 자동 refresh하기 때문에, 일반적으로는 3분마다 체킹한다. 만약 해당 부분을 수정하고 싶다면 argocd-cm에서 timeout.reconciliation의 값을 조정하면 된다.
kubectl get cm -n argocd argocd-cm -o yaml | grep timeout
timeout.hard.reconciliation: 0s
timeout.reconciliation: 180s
아직까지는 1.26.1 버전임을 확인할 수 있다.
자 이제 Sync를 눌러보자. dev-nginx v2가 배포된 것을 확인할 수 있다.
접속해봐도 1.26.2로 변경된 것을 확인할 수 있다.
Webhook을 통한 Argo CD 반영
webhook 설정할 때 주의사항이 있다. 로컬 호스트에서 반영할 경우 반드시 다음으 수정해야한다.
지금 이렇게 프라이빗 IP를 통해서 진행해보니 에러가 계속 발생했다.
webhook broken: "Payload URL resolved to a local network address that is implicitly blocked."
이 오류를 찾다보니 다음 깃허브이슈를 발견했다.
https://github.com/gogs/gogs/discussions/7048#discussioncomment-2930780
이곳에서 해당 코드 변경을 이야기하더라구요.
기본적으로 localhost에 대한 허용이 없습니다. /data/gogs/conf/app.ini 를 수정하고 컨테이너를 재시작하면 됩니다.
[security]
# specific network address, separated by commas, no port needed
LOCAL_NETWORK_ALLOWLIST = *
따라서 다음과 같이 webhook 등록이 가능해집니다.
자 이제 해당 테스트 앱을 먼저 생성해보고, webhook 반영이잘되는지 확인해봅시다.
#
echo $MyIP
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dev-nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
helm:
valueFiles:
- values-dev.yaml
path: nginx-chart
repoURL: http://$MyIP:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: dev-nginx
server: https://kubernetes.default.svc
EOF
#
kubectl get applications -n argocd dev-nginx
kubectl get applications -n argocd dev-nginx -o yaml | kubectl neat
kubectl describe applications -n argocd dev-nginx
kubectl get pod,svc,ep,cm -n dev-nginx
#
curl http://127.0.0.1:30000
open http://127.0.0.1:30000
replica count자체를 수정하는 방식으로 늘려보자. 극단적으로 12개로 파드 갯수를 늘려보자.
cd cicd-labs/ops-deploy/nginx-chart
#
sed -i -e "s|replicaCount: 2|replicaCount: 12|g" values-dev.yaml
git add values-dev.yaml && git commit -m "Modify nginx-chart : values-dev.yaml" && git push -u origin main
watch -d kubectl get all -n dev-nginx -o wide
즉각적으로 반영되는 것을 확인할 수 있다.
(참고) 혹 바로 반영되지 않는다면 다음과 같은 방식으로 트러블 슈팅을 해보기 바란다.
1. gogs 웹훅의 로그를 확인한다.
웹훅 아래 보면 최근 Deliveries라는 항목으로 웹훅에 대한 로그를 제공하고 있다.
문제가 생기면 다음과 같이 에러 응답에 대한 내용을 살펴볼 수 있고, 재전송도 가능하다.
만약 해당 응답이 200으로 정상 처리되었다면 이제 ArgoCD의 문제일 수 있다.
2. ArgoCD 로그 확인하기
기본적으로 해당 웹훅에 대한 정보는 argocd-server에서 로그를 받고 있다.
kubectl logs -f deployment/argocd-server -n argocd
다음과 같이 정상적으로 로그를 받았을 경우 이렇게 Received push event를 확인할 수 있다.
이때 주의할 점은 내가 등록한 repository와 실제 웹훅에서 오는 external 도메인과 일치해야한다는 점이다.
나의 경우 정상적으로 처리되는데도 불구하고 반영이 안되길래 찾다보니, 도착되는 url은 localhost:3000인데, argocd에서 등록된 url 정보는 172.30.1.3이기 때문에 불일치하여 반영되지 않았다. 따라서 이 둘을 일치시켜야한다.