23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
오랜만에 글을 올립니다. 사실 작년에 작업한 내용인데, 쌍둥이 육아로 이제야 올리게 되었네요.. 그 동안 작업한 내용이 꽤 되는데 하나씩 잘 정리해서 자주 공유드리겠습니다!
이 번 글은 Gitlab Runner 연동 관련 포스트입니다. 예전 AKS 상용기에서 Jenkins로 CI를 구성한 내용을 올렸는데요,
Gitlab과 연동해서 쓰기에는 runner가 더 좋을 것 같아서 새로운 프로젝트의 CI/CD를 구성할 때는 Gitlab-runner로 구성하였습니다.
어떻게 구성했는지와 구성할 때 발생했던 에러들 어떻게 해결했는지 그리고 maven 빌드가 생각보다 오래 걸려서 어떤 방법으로 빌드 속도를 개선했는지에 대한 내용을 공유드립니다.
참고자료: https://docs.gitlab.com/runner/install/kubernetes.html
Gitlab runner는 VM에도 컨테이너로도 설치가 가능합니다.
저는 k8s에 컨테이너로 gitlab-runner를 띄워야 했고 k8s에 패키지를 설치할 때 helm 만큼 편한 툴이 없기에.. helm으로 설치하게 되었습니다.
# Add the GitLab Helm repository:
helm repo add gitlab https://charts.gitlab.io
# If using Helm 2, you must also initialize Helm:
helm init
# If you are unable to access to the latest versions of GitLab Runner, you should update the chart. To update the chart, run:
helm repo update gitlab
# To view a list of GitLab Runner versions you have access to, run:
helm search repo -l gitlab/gitlab-runner아래 코드를 복사하여 values.yaml을 저장합니다. 공식 사이트에서 제공하는 values.yaml을 확인하고 싶은 경우 링크로 확인이 가능합니다.
https://gitlab.com/gitlab-org/charts/gitlab-runner/blob/main/values.yaml
gitlabUrl: <GitLab server full URL>
rbac:
create: true
runnerToken: <GITLAB-AUTHENTICATION-TOKEN>
runners:
executor: kubernetes
tags: runner1
config: |
[[runners]]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
image = "alpine"
privileged = true
[[runners.kubernetes.volumes.pvc]]
name = "gitlab-pvc"
mount_path = "/mnt/share"
securityContext:
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsNonRoot: true
privileged: true
volumeMounts:
- name: gitlab-volume
mountPath: /mnt/share
volumes:
- name: gitlab-volume
persistentVolumeCl
claimName: gitlab-pvcgitlabUrl: GitLab Host Url
runnerToken: 2번에서 획득한 token
tags: 태그명 (자유롭게 입력)
executor: "kubernetes"
runners.config
namespace = "{{.Release.Namespace}}" (고정)
image = "alpine" (ubuntu도 가능하지만 가벼운 alpine으로 설정)
privileged = true (고정) ← dind 방식으로 빌드하려면 필수로 true 설정 해야 함
securityContext
allowPrivilegeEscalation: true (고정)
readOnlyRootFilesystem: false (고정)
runAsNonRoot: true (고정)
privileged: true (고정)
여기서 maven의 빌드 속도를 개선한 핵심이 등장합니다. 바로 maven 의존성 라이브러리를 caching하는 것인데요.
runner가 매번 빌드할 때마다 의존성을 내려받는데 시간이 많이 걸리는 것을 확인하고 의존성을 caching 해두기로 결정하였습니다.
k8s이기 때문에 영구적으로 caching 파일을 유지하기 위해 Azure BlobStorage를 Volume으로 마운트할 예정입니다.
pv.yaml로 저장 → k apply -f pv.yaml로 설치
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: blob.csi.azure.com
name: gitlab-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain # If set as "Delete" container would be removed after pvc deletion
storageClassName: azureblob-fuse-premium
mountOptions:
- -o allow_other
- --file-cache-timeout-in-seconds=120
csi:
driver: blob.csi.azure.com
readOnly: false
# volumeid has to be unique for every identical storage blob container in the cluster
# character `#` is reserved for internal use and cannot be used in volumehandle
volumeHandle: gitlab-volume
volumeAttributes:
containerName: gitlab-runner # container name
nodeStageSecretRef:
name: blob-secret
namespace: gitlabpvc.yaml로 저장 → k apply -f pvc.yaml로 설치
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: gitlab-pvc # pvc naming
namespace: gitlab
spec:
accessModes:
- ReadWriteMany # access mode
storageClassName: azureblob-fuse-premium # created by AKS
volumeName: gitlab-pv
resources:
requests:
storage: 10Gi드디어 gitlab-runner를 설치합니다. 저는 helm 3 기반으로 설치하였습니다.
# Once you have configured GitLab Runner in your values.yaml file, run the following:
# For Helm 2
helm install --namespace <NAMESPACE> --name gitlab-runner-1 -f <CONFIG_VALUES_FILE> gitlab/gitlab-runner
# For Helm 3
helm install --namespace <NAMESPACE> gitlab-runner-1 -f <CONFIG_VALUES_FILE> gitlab/gitlab-runner
# 예시 (Helm 3)
helm install --namespace gitlab gitlab-runner-1 -f values.yaml gitlab/gitlab-runnergitlabUrl: <GitLab server full URL>
rbac:
create: true
runnerToken: <GITLAB-AUTHENTICATION-TOKEN>
runners:
executor: kubernetes
tags: runner2 # 태그명은 기존과 다르게 설정해야 됨
runners:
config: |
[[runners]]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
image = "alpine"
privileged = true
securityContext:
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsNonRoot: true
privileged: truehelm install --namespace gitlab-runner-2-fvalues2.yaml gitlab/gitlab-runner
Gitlab-Runner를 생성하면 토큰 기반으로 Gitlab에서 runner가 있는지 확인합니다.
아래 처럼 GitLab에 UI로 접속하여 Assigned project runners에 내가 만든 runner 옆에 초록색 불이 들어왔는지 확인하여 잘 연동 되었는지를 확인합니다.
Gitlab Runner를 통해 이제 실제 소스 프로젝트를 빌드해 볼 시간입니다. 저는 Maven 기반의 Spring 프로젝트를 이용하여 빌드 테스트를 진행하였습니다.
GitLab 프로젝트 접속 > Settings > CI/CD > Variables 'Expand' 클릭
'Add variable' 버튼 클릭
추가해야 할 Variables (AZURE는 사용하시는 레지스트리에 따라 변경)
AZURE_REGISTRY_URL: 컨테이너 레지스트리의 full url
AZURE_LOGIN_TOKEN: 레지스트리 로그인 토큰
MANIFEST_URL: k8s manifest 파일이 저장된 gitlab 경로
MANIFEST_USERNAME: k8s manifest 파일이 저장된 gitlab의 액세스 토큰명
MANIFEST_TOKEN: k8s manifest 파일이 저장된 gitlab의 액세스 토큰값
gitlab-runner를 빌드하기 위해서는 프로젝트 소스 최상위 디렉토리에 .gitlab-ci.yaml 파일을 생성해야 합니다.
이 gitlab-ci.yaml을 잘 작성하는 것이 매우 중요한데요.
yaml을 작성할 때의 주요 전략과 각 라인이 의미하는 바를 정리했으니 한 번 읽어보시고 적용 해보시면 좋을 것 같습니다.
stage는 'package' 단계와 'build and push and k8s_manifest_update' 단계로 나눔
원래는 하나의 단계로 합치려고 했으나 maven package 하는 것은 maven image에서 진행하고 나머지는 docker image에서 진행하기 위해 2개의 stage로 분리
package 단계에서는 mvn 빌드를 하고 결과 아티팩트(jar)를 PVC로 shared storage에 저장
build and push and k8s_manifest_update 단계에서는 해당 jar를 가지고 도커 빌드한 후 생성된 image를 ACR에 업로드
mvn package 부분이 원래 dockerfile 안에 있었으나 gitlab-ci.yaml로 바깥으로 뺀 이유는 mvn 빌드할 때 필요한 의존성 라이브러리를 shared volume에 caching하기 위함
빌드 속도 개선을 위해 mvn 명령어에 일부 옵션 추가
stages:
- package
- build and push and k8s_manifest_update
variables:
IMAGE: "${CI_PROJECT_NAME}"
TAG: "${CI_PIPELINE_ID}"
AZURE_REGISTRY_URL: "$AZURE_REGISTRY_URL"
AZURE_REGISTRY_PASSWORD: "$AZURE_LOGIN_TOKEN"
MANIFEST_URL: "$MANIFEST_URL"
MANIFEST_USERNAME: "$MANIFEST_USERNAME"
MANIFEST_TOKEN: "$MANIFEST_TOKEN"
MAVEN_OPTS: "-Dmaven.repo.local=/mnt/share/.m2/repository"
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- /mnt/share
package:
stage: package
image: maven:3.8.4-openjdk-17-slim
script:
# maven 빌드 결과물(jar)을 Shared 저장소에 업로드
- mvn -T 1C -f pom.xml clean package -Dmaven.test.skip -DskipTests -Dmaven.javadoc.skip=true
- rm -rf /mnt/share/target
- mkdir /mnt/share/target
- cp ./target/$IMAGE-0.0.1-SNAPSHOT.jar /mnt/share/target/$IMAGE-0.0.1-SNAPSHOT.jar
- echo 'package script end!'
only:
- develop
- stage
- live
build and push and k8s_manifest_update:
image: docker:24.0.5
stage: build and push and k8s_manifest_update
services:
- name: docker:dind
command: ["--tls=false"]
before_script:
- |
if [[ "$CI_COMMIT_REF_NAME" == "develop" ]]; then
echo "branch: develop"
export ENV="dev"
elif [[ "$CI_COMMIT_REF_NAME" == "staging" ]]; then
echo "branch: staging"
export ENV="stg"
elif [[ "$CI_COMMIT_REF_NAME" == "main" ]]; then
echo "branch: main"
export ENV="live"
else
echo "Unsupported branch: $CI_COMMIT_REF_NAME"
exit 1
fi
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
script:
# ACR 로그인
- echo [LOGIN] ACR LOGIN START
- echo $AZURE_REGISTRY_PASSWORD | docker login $AZURE_REGISTRY_URL -u token --password-stdin
- echo [LOGIN] ACR LOGIN SUCCESS
- echo "ENV ${ENV}"
# Docker 빌드 & ACR PUSH
- echo [BUILD] 'build start'
- mv /mnt/share/target .
- docker build -t "$IMAGE:$TAG" .
- echo [BUILD] 'build success!'
- docker tag "$IMAGE:$TAG" "$AZURE_REGISTRY_URL/$IMAGE/${ENV}/$IMAGE:$TAG"
- echo [PUSH] 'push start'
- docker push "$AZURE_REGISTRY_URL/$IMAGE/${ENV}/$IMAGE:$TAG"
- echo [PUSH] 'push success!'
# k8s manifest git 접속 & Clone
- apk add --no-cache git
- git config --global user.name "${GITLAB_USER_NAME}"
- git config --global user.email "${GITLAB_USER_EMAIL}"
- git clone https://$MANIFEST_USERNAME:$MANIFEST_TOKEN@$MANIFEST_URL
- cd <manifest 프로젝트 이름>
# docker image 버전 최신화 및 PUSH
- sed -i "s/$IMAGE:.*\$/$IMAGE:$TAG/g" $IMAGE/${ENV}/deployment.yaml
- git add $IMAGE/${ENV}/deployment.yaml
- git commit -m "[UPDATE] $IMAGE $TAG image versioning"
- git push origin main
- echo 'build and push and K8S Manifest Update script end!'
only:
- develop
- stage
- livevariables:
IMAGE: "${CI_PROJECT_NAME}" ← 프로젝트명 (Gitlab 기본 환경변수)
TAG: "${CI_PIPELINE_ID}" ← PipelineId (Gitlab 기본 환경변수)
AZURE_REGISTRY_URL: "$AZURE_REGISTRY_URL"
AZURE_REGISTRY_PASSWORD: "$AZURE_LOGIN_TOKEN"
MANIFEST_URL: "$MANIFEST_URL"
MANIFEST_USERNAME: "$MANIFEST_USERNAME"
MANIFEST_TOKEN: "$MANIFEST_TOKEN"
MAVEN_OPTS: "-Dmaven.repo.local=/mnt/share/.m2/repository" ← maven local repository 경로 (설정 시 maven 빌드할 때 이 경로가 의존성 repo로 사용됨)
cache:
key: $CI_COMMIT_REF_SLUG ←Gitlab 기본 환경변수로 브랜치명의 lowercase 버전 (예: develop)
paths:
/mnt/share ← cache가 저장되는 경로
package:
stage: package ← stage 이름은 최상단의 stages에서 리스트로 열거한 이름과 동일해야 함
image: maven:3.8.4-openjdk-17-slim ← package 단계의 image
script: # maven 빌드 결과물(jar)을 Shared 저장소에 업로드
mvn -T 1C -f pom.xml clean package -Dmaven.test.skip -DskipTests -Dmaven.javadoc.skip=true
속도 개선을 위해 추가한 명령어
T -1C: 다중 스레드 사용 (-T 1C는 Maven이 사용 가능한 코어당 하나의 스레드를 사용함을 의미)
Dmaven.test.skip -DskipTests: 테스트 생략
Dmaven.javadoc.skip=true: javadoc 생성 건너띄기
rm -rf /mnt/share/target ← 아티팩트를 저장할 target 폴더 삭제
mkdir /mnt/share/target ← target 폴더 재생성
cp ./target/$IMAGE-0.0.1-SNAPSHOT.jar /mnt/share/target/$IMAGE-0.0.1-SNAPSHOT.jar ← shared storage에 아티팩트 jar 저장
only: ← only 절이 있으면 해당 브랜치에서만 stage가 작동 (develop, stage, live)
build and push and k8s_manifest_update:
image: docker:24.0.5 ← build/push/manifest_update 하는 단계는 docker image에서 수행
stage: build and push and k8s_manifest_update ← stage 이름은 최상단의 stages에서 리스트로 열거한 이름과 동일해야 함
services:
name: docker:dind ← 실제 서비스는 dind(docker in docker) image에서 수행
command: ["–tls=false"] ← Is the docker daemon running? 에러 해소를 위해 필요 (아래 참고)
before_script: ← 이 아래 절은 브랜치에 따라 env를 설정하는 구문 - | if [[ "$CI_COMMIT_REF_NAME" == "develop" ]]; thenecho "branch: develop"export ENV="dev"elif [[ "$CI_COMMIT_REF_NAME" == "staging" ]]; thenecho "branch: staging"export ENV="stg"elif [[ "$CI_COMMIT_REF_NAME" == "main" ]]; thenecho "branch: main"export ENV="live"elseecho "Unsupported branch: $CI_COMMIT_REF_NAME"exit 1fi
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
script:
ACR 로그인
echo $AZURE_REGISTRY_PASSWORD | docker login $AZURE_REGISTRY_URL -u token --password-stdin ← ACR 로그인 명령어
Docker 빌드 & ACR PUSH
mv /mnt/share/target . ← target 폴더 통째로 이동 (이 때 shared storage에서 삭제됩니다.)
docker build -t "$IMAGE:$TAG" . ← 도커 컨테이너로 빌드
docker tag "$IMAGE:$TAG" "$AZURE_REGISTRY_URL/$IMAGE/${ENV}/$IMAGE:$TAG" ← ACR에 PUSH할 이름으로 이미지 태그 설정
docker push "$AZURE_REGISTRY_URL/$IMAGE/${ENV}/$IMAGE:$TAG" ← ACR에 PUSH
k8s manifest git 접속 & Clone
apk add --no-cache git ←
git config --global user.name "${GITLAB_USER_NAME}"
git config --global user.email "${GITLAB_USER_EMAIL}"
git clone https://$MANIFEST_USERNAME:$MANIFEST_TOKEN@$MANIFEST_URL ← Git Clone
cd <manifest 프로젝트명>
docker image 버전 최신화 및 PUSH
sed -i "s/$IMAGE:.*$/$IMAGE:$TAG/g" $IMAGE/${ENV}/deployment.yaml ← deployment.yaml에서 이미지 버전 변경
git add $IMAGE/${ENV}/deployment.yaml
git commit -m "[UPDATE] $IMAGE $TAG image versioning" ← git commit
git push origin main ← git push
only: ← only 절이 있으면 해당 브랜치에서만 위 stage가 작동
Reference
GitLab에서 기본적으로 정의되어 있는 환경변수: https://allroundplaying.tistory.com/55
maven m2 repository 캐싱: https://sh970901.tistory.com/138
앞에서 말씀드린 것과 같이 maven 기반 Spring 프로젝트의 빌드 시간이 꽤 오래걸렸습니다. (처음 4분 44초)
이것을 최종 11초로 빌드 시간을 단축 하였는데요, 아래와 같은 방법으로 진행하였습니다.
최초 빌드 시간
메이븐 의존성 라이브러리 캐시 이후 (여기서 드라마틱하게 감소)
maven 옵션 중 테스트 및 javadoc 생성 skip 처리 (-Dmaven.test.skip -DskipTests -Dmaven.javadoc.skip=true)
다중 스레드 사용 (-T 1C)
Reference
Maven 빌드 속도개선: https://rainbound.tistory.com/entry/maven-%EB%B9%8C%EB%93%9C-%EC%86%8D%EB%8F%84-%EA%B0%9C%EC%84%A0
Gitlab과 Gitlab-runner를 연동할 때, Gitlab UI를 통해서 프로젝트 관련 설정의 일부를 변경해주어야 연동이 가능합니다.
gitlab 접속 → Settings → CI/CD → Runners 'Expand' 클릭 → Assigned project runners에서 수정(연필모양) 버튼 클릭 → "Run untagged jobs"에 체크 후 저장
gitlab 접속 → Settings → Repository → Protected branches 'Expand' 클릭 → Add protected branch
develop 브랜치에서 commit하고 push하면 자동으로 Gitlab-runner에서 빌드가 구동 됨
빌드가 시작되면 Gitlab 좌측 탭에서 Build > Pipelines를 클릭하면 실행되는 Pipeline을 볼 수 있음
#으로 시작하는 Pipeline ID를 클릭하면 빌드 진행상황을 자세히 볼 수 있음
돌아가고 있는 stage를 클릭하면 상세 로그를 확인할 수 있음
드디어 빌드 성공!
Azure Portal에 접속해서 ACR(Azure Container Registry)에 이미지가 잘 저장되었는지 확인
docker login ACR -> docker image ls로 확인
ArgoCD 접속해서 APP DIFF로 manifest 파일의 도커이미지 버전 변경됨을 확인
Gitlab Runner 연동할 때 생각보다 많은 에러가 발생했습니다. 많은 try&error(라고 읽고 삽질이라 씀..)를 통해 빌드를 성공할 수 있었는데요..
발생했던 에러들 어떻게 해결 했는지 공유드립니다.
해결: values.yaml 안에 있는 image를 docker에서 alpine으로 변경 후 재 설치
runners:
config: |
[[runners]]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
image = "alpine" <- 이 부분 수정. ubuntu도 가능. docker는 안됨
privileged = true해결: gitlab 접속 → runner edit → "Run untagged jobs"에 체크 후 저장
$ docker login -u token -p $AZURE_LOGIN_TOKEN $AZURE_REGISTRY_URL해결: ci.yaml 파일에서 variable로 선언한 뒤 스크립트에서 사용하도록 변경하니 에러 해결!
$ echo $AZURE_REGISTRY_PASSWORD | docker login $AZURE_REGISTRY_URL -u token --password-stdin원인: test 하던 develop 브랜치에 protected 설정이 안 되어 있었음
해결: develop 브랜치를 protected 브랜치로 설정하니까 문제 해결
원인: privileged가 기본적으로 false로 되어 있어 true로 설정해주어야 함
해결: values.yaml에 privileged: true 추가 후 해결
runners:
config: |
[[runners]]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
image = "alpine"
privileged = true <- 이 부분securityContext:
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsNonRoot: true
privileged: true <- 이 부분가장 해결하기 어려웠던 문제. 해결하기 위해 Runner의 구조 부터 파악.
우선 gitlab 공식 홈페이지에서 gitlab-runner 설정하는 방법에 대해 확인
주요 팩터 1: executor
gitlab-runner가 build할 때 어떤 방식으로 실행하느냐인데, docker, shell, kubernetes 중에서 선택 가능. 우리는 kubernetes를 사용할 수 있으므로 kubernetes로 선택
주요 팩터 2: runner의 image
gitlab-runner가 build할 runner를 띄울 때 image를 정의할 수 있는데 기본값인 alpine 사용
주요 팩터 3: ci.yaml에서 service에 docker를 실행시키는 방법 정의
dind (Docker in Docker)를 사용하느냐, 일반 docker socket을 사용하느냐 결정할 수 있음
두 개의 장단점은 링크에서 확인 가능 https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
공식홈페이지에서 알려준대로 똑같이 설정해도 계속 Is the docker daemon running? 에러 발생하였고 문제에 대해 search하던 중 비슷한 사례를 발견했습니다.
https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27300
그 중 "Ilya Frolov" 의 답변을 읽고 적용 해 봤는데요. 원인과 해결방안은 아래와 같습니다.
원인: docker:dind v20은 TLS를 명시적으로 비활성화하지 않으면 16초 동안 잠자고, 이로 인해 빌드 컨테이너가 dockerd 컨테이너보다 일찍 시작되는 경쟁 상태가 발생하는 것을 확인.
이 경우 루프백 주소에 연결하면 즉시 오류가 발생하지만, 네트워크 주소에 간접적으로 연결하면 dockerd가 잠자기를 기다리는 활성 프로브로 작동함.
도커가 데몬의 시작 속도를 늦춰 TLS 인증을 강제하려는 시도가 있음. 따라서 명시적으로 tls=false 설정을 하여 TLS 인증을 우회하도록 설정
해결: gitlab-ci.yaml에 아래 내용 추가
services:
- name: docker:dind
command: ["--tls=false"]상세하게 작성하다 보니 길이 많이 길어졌네요.. Gitlab Runner 핵심 절차를 요약하자면 아래와 같습니다.
Gitlab-runner Helm Chart로 설치 (k8s)
Gitlab Token으로 Runner 연결
.gitlab-ci.yaml 파일 작성하여 Spring 프로젝트를 Runner에서 빌드
빌드 속도 단축을 위해 maven 의존성 캐싱 및 멀티 스레드 처리
GitLab Runner로 CI/CD를 구성하는 분께 조금이나마 도움이 되셨길 바라며 마치겠습니다. 감사합니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.