DevOps & Infra/Docker

[GitLab/CI] GitLab에서 CI 작업하기

턴태 2023. 11. 27. 17:46

CI/CD를 사용할 때는 gitlab을 주로 사용한다고 합니다. GitHub와 달리 기본적으로 CI/CD에 집중한 원격 저장소라는 점이 특징이기 때문입니다. GitHub가 많은 사용자와 자유로운 기능이 장점이라면, GitLab은 통합적으로 서비스를 사용할 수 있다는 것이 장점이라 각각 고유의 메리트가 있습니다.

 

GitLab 레포지터리에 들어가보면 바로 Set CI/CD 버튼이 보이네요.

 

 

이번에는 gitlab ci를 통해 웹서버를 레포지터리에 업로드하고, 원격으로 웹서버를 띄우는 간단한 실습을 진행해보겠습니다. CI 단계만 진행하므로 서버로의 배포는 수동으로 진행합니다.

준비물

간단한 테스트를 위한 서버 애플리케이션을 생성합니다. 기본적으로 헬스 체크 기능을 만들어 연결을 확인했습니다.

 

레지스트리는 ECR private 레포지터리를 사용하였으며, 서버는 EC2 t2-micro 인스턴스를 사용했습니다.

CI 파일 작성

 

gitlab은 .gitlab 디렉터리에 파일을 생성해서 ci를 진행합니다.

 

가장 심플하게 작성한 ci 상세는 다음과 같습니다.

variables:
  APP_NAME: nodejs-server-gitlab
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: ""

stages:
  - build
  - push

create-env-file:
  only:
    - develop
  stage: build
  script:
    - echo DB_NAME=$DB_NAME >> .env
    - echo DB_USERNAME=$DB_USERNAME >> .env
    - echo DB_PASSWORD=$DB_PASSWORD >> .env
    - echo DB_HOST=$DB_HOST >> .env
    - echo DB_PORT=$DB_PORT >> .env
    - echo REDIS_URL=$REDIS_URL >> .env
    - echo REDIS_USERNAME=$REDIS_USERNAME >> .env
    - echo REDIS_PASSWORD=$REDIS_PASSWORD >> .env
    - echo JWT_SECRET=$JWT_SECRET >> .env
  artifacts:
    paths:
      - .env

docker-build:
  only:
    - develop

  stage: push
  
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - apk add --no-cache curl jq python3 py3-pip
    - pip install awscli
    - aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY
  script:
    - docker build -t $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID .
    - docker push $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID

 

차근차근 단계별로 살펴보자면 다음과 같습니다.

📍 variables

variables:
  APP_NAME: nodejs-server-gitlab
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: ""
  • APP_NAME - 이미지 이름입니다. 굳이 사용하지 않아도 됩니다.
  • DOCKER_HOST: tcp://docker:2375 - Docker가 데몬에 연결하는 데 사용되는 호스트입니다. tcp://docker:2375는 Docker 데몬이 돌아가고 있는 호스트와 포트입니다. gitlab의 CI/CD 파이프라인은 기본적으로 도커 컨테이너 내에서 작동하게 되는데, 각각의 job 단계들이 docker의 image를 사용합니다. 그래서 dind, 도커 안에 도커를 사용하는 방식을 사용해 docker client가 내부에서 도커를 돌릴 수 있게 됩니다.
  • DOCKER_DRIVER - Docker 데몬이 사용하는 저장 드라이버를 설정합니다.
  • DOCKER_TLS_CERTDIR - Docker TLS(전송 계층 보안) 연결을 사용할 때 인증서 파일이 저장된 디렉토리를 나타냅니다.

DOCKER 관련 변수는 gitlab runner를 사용하지 않는 경우 굳이 변수를 설정하지 않아도 gitlab ci에서 사용이 가능합니다.

📍 stages

stages:
  - build
  - push

 

gitlab ci의 단계입니다. github actions의 job 단계와 비슷합니다. stages를 통해 해당 ci 파이프라인에서 수행할 단계들을 미리 선언할 수 있습니다. 여기서 stages 배열에 넣은 단계 순서대로 작업이 진행됩니다. 위의 경우 build 스테이지 후 push 스테이지가 실행됩니다. stage를 선언하지 않으면 기본적으로 stage 속으로 선언하는 job들이 병렬적으로 실행됩니다.

📍 create-env-file

create-env-file:
  only:
    - develop
  stage: build
  script:
    - echo DB_NAME=$DB_NAME >> .env
    - echo DB_USERNAME=$DB_USERNAME >> .env
    - echo DB_PASSWORD=$DB_PASSWORD >> .env
    - echo DB_HOST=$DB_HOST >> .env
    - echo DB_PORT=$DB_PORT >> .env
    - echo REDIS_URL=$REDIS_URL >> .env
    - echo REDIS_USERNAME=$REDIS_USERNAME >> .env
    - echo REDIS_PASSWORD=$REDIS_PASSWORD >> .env
    - echo JWT_SECRET=$JWT_SECRET >> .env
  artifacts:
    paths:
      - .env

 

.env 파일을 생성합니다. 위 단계는 github actions에서 secrets를 생성하는 것과 유사한 과정입니다. Gitlab 레포지터리로 들어가 Settings > CI/CD > Variables에서 변수를 설정하면 이 변수를 ci 파이프라인에서 사용할 수 있습니다.

 

여기에 variables가 사용할 수 있는 속성이 3개가 있습니다. 이걸 잘 활용하는 게 중요합니다. 아무 설명도 안 읽고 하루 종일 ci를 시도했었는데 속성을 제대로 설정하지 않은 게 문제였습니다. 😅

  • Protected - protected 브랜치나 protected 태그에서만 사용할 수 있습니다. Settings > Repository > Protected branches, Protected tags 에서 추가할 수 있습니다. 만약 protected branch가 아닌 브랜치에서 ci를 수행하면 읽어오지 못합니다.
  • Masked - ci job 로그에서 가려집니다.

  • Expanded - 이 속성은 자세한 설명이 없는데, 변수 확장을 위해 쓰이는 것으로 보입니다. 예를 들어 VAR1=Hello 라면, VAR2=$VAL1_World로 사용했을 때 VAR2의 값은 Hello_World로 할당되는 것입니다.

신기한 점은 ci 상세에서 사용한 변수가 덮어써지는 경우가 있다는 것입니다. 예를 들어서,

aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY

 

이렇게 리전을 하드코딩하여 저장했고, CI/CD variables에 AWS_DEFAULT_REGION 변수를 저장하고 Masked 속성을 부여했는데 job log를 보니까 덮어써지는 것을 볼 수 있었습니다.

 

보통 .env 파일을 생성할 때 github actions와 비슷한 방법으로 echo를 표준 입력으로 사용해 .env 파일에 저장합니다. 이때, .env 파일을 artifacts로 저장해 두어야 합니다.

create-env-file:
  artifacts:
    paths:
      - .env

 

artifacts로 저장하지 않으면 다른 스테이지 작업을 수행할 때, 만들어둔 파일이 스테이지에서 사용되지 않습니다. 이것 때문에 또 시간을 많이 잡아먹었습니다. 🚬

only:
  - develop

 

only 필드의 값에 브랜치를 설정하면, 해당 브랜치에서만 stage가 동작하게 설정할 수 있습니다.

📍 docker-build

docker-build:
  only:
    - develop

  stage: push

  image: docker:latest
  services:
    - docker:dind
  before_script:
    - apk add --no-cache curl jq python3 py3-pip
    - pip install awscli
    - aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY
  script:
    - docker build -t $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID .
    - docker push $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID

 

마지막 docker 단계입니다.

 

image는 docker를 사용하며, services를 통해 추가 이미지를 설정할 수 있습니다. docker 컨테이너 안에서 다시 docker 클라이언트를 사용해야 하므로, dind 태그 이미지를 추가로 지정해주었습니다. services 필드의 예시는 다음과 같습니다.

job:
  services:
    - php:7
    - node:latest
    - golang:1.10
  image: alpine:3.7
  script:
    - php -v
    - node -v
    - go version

 

예를 들어, php, node, go를 사용하는 Job이라면 services에 php, node, golang 이미지를 추가로 지정할 수 있습니다.

before_script:
  - apk add --no-cache curl jq python3 py3-pip
  - pip install awscli
  - aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REPOSITORY

 

본격적으로 script를 수행하기 전에 다른 스크립트를 수행할 수 있습니다. aws ecr을 사용하기 때문에 aws ecr 레포지터리에 로그인이 필요합니다. awscli를 설치한 다음, ecr 패스워드를 획득해주도록 합니다.

 

여기서 aws 설정이 필요합니다. Gitlab CI/CD variables에 미리 access key와 secret access key를 등록해두면 됩니다. oidc를 사용한 접근은 제대로 성공하지 못해서 access key와 secret access key를 등록하여 자격증명을 마쳤습니다.

 

여기서 사용할 유저는 ecr에 로그인하고 image까지 Push 할 권한이 필요할 겁니다. 그렇기 때문에 User 권한으로 아래의 권한들을 추가합니다.

"ecr:GetAuthorizationToken"
"ecr:BatchCheckLayerAvailability"
"ecr:CompleteLayerUpload"
"ecr:InitiateLayerUpload"
"ecr:PutImage"
"ecr:UploadLayerPart"

 

이제 본격적으로 이미지를 빌드하고 푸시합니다.

  script:
    - docker build -t $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID .
    - docker push $ECR_REPOSITORY/$APP_NAME:$CI_PIPELINE_IID

 

여기서 사전에 설정해두지 않은 CI_PIPELINE_IID가 등장하는데요, 이건 Gitlab에서 미리 만들어둔 변수들입니다. 아래 링크에서 모든 변수들을 확인할 수 있습니다.

https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

 

Predefined variables reference | GitLab

GitLab product documentation.

docs.gitlab.com

 

태그는 원하는 태그명을 사용하면 되므로 자유롭게 설정하시면 됩니다.

 

이제 push나 merge 이벤트를 발생시킨 다음 Build > Pipelines에 들어가서 파이프라인을 확인하면 현재 진행중인 ci 단계를 확인할 수 있습니다.

 

모든 단계가 성공적으로 수행돼, 이미지가 push 된 것을 확인할 수 있습니다.

 

이제 push 된 이미지를 ec2 에서 pull 받아 컨테이너를 실행시키면 끝입니다.

 

정리하며

출처: https://aws.amazon.com/ko/blogs/devops/unlock-the-power-of-ec2-graviton-with-gitlab-ci-cd-and-eks-runners/

 

아직은 gitlab에 관해 확실하게 학습하지 못해서 간단한 ci 파이프라인만 생성했는데, ci/cd까지 수행할 수 있도록 숙련하면 좋을 것 같습니다. Gitlab의 runner를 사용하면 외부에서 ci 파이프라인을 동작시킬 수 있습니다. 예를 들어, ec2 인스턴스에 gitlab runner를 설치해 에이전트로 등록을 해두면, gitlab ci가 runner를 트리거하여 ec2 인스턴스 속 runner가 해당 파이프라인을 수행하여 배포까지 할 수 있기 때문입니다. 특히 kubernetes로의 배포까지 통합적으로 수행할 수 있는 게 아주 강력한 장점이라고 생각이 드네요.

참고

https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker

 

Use Docker to build Docker images | GitLab

GitLab product documentation.

docs.gitlab.com

https://docs.gitlab.com/ee/ci/services/

 

Services | GitLab

GitLab product documentation.

docs.gitlab.com

https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker

 

Use Docker to build Docker images | GitLab

GitLab product documentation.

docs.gitlab.com

https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

 

Predefined variables reference | GitLab

GitLab product documentation.

docs.gitlab.com