20.깃허브 액션을 이용한 CI 시스템 구축
이번 장은 깃허브 액션을 이용하여 CI 시스템을 구축하는 방법을 알아봅니다.
이미 많이 들어 보셨겠지만 CI 시스템은 “Continuous Integration”의 약자로, 소프트웨어 개발 프로세스에서 자동화된 빌드, 테스트, 이미지 업로드까지 지원하는 중요한 도구입니다. CI 시스템은 개발자들이 애플리케이션 코드를 빠르게 통합하고 오류를 빠르게 발견하여 애플리케이션의 품질을 유지하고 개선하는 데에 사용됩니다.
개발자들이 새로운 코드를 버전 관리 시스템(깃허브)에 푸쉬하면 CI 시스템이 해당 코드를 감지하고 자동으로 빌드, 테스트, 이미지 업로드까지 진행합니다. 원하는 조건 만족 시 자동으로 실행하고 이미지 Build 시간을 줄이는 것이 중요한 요소입니다.
현재 주로 사용하는 CI 도구로는 Jenkins, GitLab CI/CD, Travis CI, CircleCI 등이 있는데 최근에는 깃허브 액션을 가장 많이 사용하는 추세 같습니다. 아무래도 거의 모든 개발자들이 깃허브 계정을 가지고 있어 진입 장벽이 낮고 사용하기가 편리하기 때문인 것 같습니다. 데브옵스 입장에서는 어떻게 보면 CI는 단순한 작업이라 CI 용도로 추가 도구를 사용해서 관리 포인트를 증가하는 것보다는 깃저장소와 통합된 CI 시스템을 더 선호합니다.
깃헙 액션에 비하여 가장 많이 사용하는 도구 중 하나인 젠킨스는 추가 플러그인 관리 등이 복잡하고 초기 설정에 까다로워 진입 장벽이 있습니다.
아마 대부분의 스타트업, 중소 기업 등도 깃허브 Team Plan(사용자당 월 $6)을 사용하고 있을텐데 해당 Plan은 깃허브 액션을 3,000분까지 무료로 사용할 수 있어 비용 부담도 크지 않습니다. 그리고 깃허브 액션을 EC2 또는 VM에서 Self-Hosted로 사용하면 추가 비용 부담없이 사용할 수 있어 무료 3,000분 이상을 사용하는 조직도 부담이 없습니다.
그럼 실습으로 자세하게 알아보겠습니다.
먼저 이번 장의 실습 리스트를 정리합니다.
실습 내용
- AWS IAM Role & Identity Provider 생성
- AWS ECR 생성
- 도커 파일 & 샘플 앱 코드 생성
- CI 테스트 용도 깃헙 레포 생성 – 환경 변수 등록
- 깃헙 액션 workflow 파일 생성
- 이미지 tag 정보를 깃헙 소스 태그 정보와 일치
- 이미지 build & push 깃헙 액션 작업 검증
- 생성한 ECR 이미지 기반 파드 생성(이미지 검증)
아마도 다른 CI 설정과 비슷한 프로세스일 것 같으며 조금 차이가 있다면 이미지 태그 정보를 깃헙 소스 태그 정보로 사용한다는 것과 ECR 인증 정보를 영구 자격 증명의 사용자 기반이 아닌 토큰 기반의 임시 자격 증명을 사용한다일 것 같습니다.
그럼 각 단계 별로 실습으로 알아겠습니다.
1. AWS IAM Role & Identity Provider 생성
인증 용도로 보안 강화를 위하여 사용자가 아닌 AWS IAM Role을 사용합니다. Role의 Trust Relations은 깃헙을 사용합니다. 깃허브 공식 문서에 자세한 설정을 확인할 수 있습니다.
깃헙 액션 워크 플로가 사용할 인증을 Cloud Provider, AWS에서 OIDC Trust를 제공하고 AWS의 Role 과 해당 Role이 사용하는 리소스를 정의한 Policy를 미리 정의합니다. 해당 설정을 이용하여 향후 깃헙 액션 워크플로우에서 인증을 요청하면 AWS에서 임시 토큰을 발급하여 인증 단계를 완료합니다. 해당 요구 사항을 만족하도록 AWS에 관련 설정을 진행합니다.
그럼 AWS 콘솔에서 Identity Provider를 생성합니다.
위 스크린 캡쳐와 같이 IAM – Identity Provider 메뉴에서 새로운 ‘Identity Provider 추가’를 선택하고 OpenID Connect Provider URL과 Audience에 https://token.actions.githubusercontent.com과 sts.amazonaws.com 를 각각 입력합니다.
해당 Provider를 이용하여 IAM Role 인증을 진행합니다.
다음으로 IAM Role을 생성합니다. 콘솔 메뉴에서 Role – ‘Create Role’를 선택하고 아래 ‘Select trusted entity’에 웹을 통한 자격 증명 ‘Web identity’를 선택하고 앞에서 만든 깃헙 액션 설정을 선택합니다.
‘GitHub organization’는 조직 설정을 지정하거나 개인 계정이면 개인의 계정 ID(예: junghoon2)를 등록합니다.
이 후 권한 설정 부분에서 ECR 관련 정책(policy)를 선택합니다. ECR 이미지를 조회하고 Push 할 수 있는 권한을 아래와 같이 선택하였습니다.
이제 Role 설정이 완료되었고 해당 Role을 기준으로 깃헙 액션 Workflow에서 인증 용도로 사용합니다.
2. AWS ECR 생성
깃헙 액션으로 빌드한 이미지를 업로드할 AWS ECR을 생성합니다. 이미지 리포지토리 종류로 Harbor, 도커허브, 깃허브 등도 사용할 수 있으나 AWS EKS 환경이면 추가 인증 없이 편리하게 사용할 수 있고 속도와 비용에서 경쟁력이 있는 ECR을 사용하는 것이 괜찮은 선택입니다.
ECR은 AWS 콘솔에서 생성할 수 있으나 가능하면 테라폼 등의 IaC 도구를 사용하는 것을 권고합니다. 아래는 ECR 생성 테라폼 코드입니다.
resource "aws_ecr_repository" "github_actions_ecr" { name = "python-flask" # ECR 리포지토리 이름 image_tag_mutability = "MUTABLE" }
테라폼 코드도 매우 간단합니다. 어렵지 않게 ECR을 테라폼 코드로 만들 수 있습니다.
- resource “aws_ecr_repository”
ECR을 생성하기 위하여 테라폼 리소스로 aws_ecr_repository를 사용합니다. 테라폼 리소스의 이름 github_actions_ecr은 임의로 지정합니다. - name
ECR 리포지토리 이름입니다. 다른 리포지토리와 구분할 수 있게 역시 임의로 지정합니다. - image_tag_mutability
태그를 변경할 수 있느냐 설정으로 MUTABLE, IMMUTABLE을 옵션이 있습니다. 개별 요구 사항에 맞게 적절하게 선택하면 됩니다. 필자는 변경 가능한 MUTABLE 옵션을 선택하였습니다.
테라폼 코드가 완성되어 테라폼을 이용하여 자원을 생성합니다.
tf init tf plan -out planfile tf apply
적용이 완료되면 AWS ECR 콘솔에서 아래와 같이 ‘python-flask’ 이름의 리포지토리를 확인할 수 있습니다.
3. 도커 파일 & 샘플 앱 코드 생성
샘플 용도로 간단한 앱과 Dockerfile을 생성합니다. 전체 코드는 깃허브 주소를 참고하시고 아래는 Dockerfile 예제입니다.
# 베이스 이미지로 Python 3.8을 사용합니다. FROM python:3.8 # 작업 디렉토리 설정 WORKDIR /app # Python 의존성 파일을 컨테이너로 복사 COPY requirements.txt requirements.txt # 의존성 설치 RUN pip install -r requirements.txt # 나머지 파일을 작업 디렉토리로 복사 COPY . . # Flask 애플리케이션 실행 CMD [ "python", "./app.py" ]
파이썬 기본 이미지를 기반으로 의존성 파일(flask)을 설치하고 실행 명령어로 app.py으로 지정한 간단한 도커파일입니다. 이 외 app.py, requirement.txt 파일은 공유한 깃허브 파일을 참조합니다.
4. CI 테스트 용도 깃허브 레포 생성 – 환경 변수 등록
이제 실제 깃허브 액션 파일을 실행할 깃허브 리포지토리를 생성합니다.
리포지토리 이름은 임의로 지정할 수 있습니다. 생성이 완료되면 액션 파일에서 사용할 환경 변수를 등록합니다. ‘Settings’의 ‘Actions secrets and variables’에 아래와 같이 ‘AWs_ECR_REPO’와 ‘AWS_REGION’ 정보를 각자 환경에 맞게 등록합니다.
AWS_ECR_REPO는 실제 ECR 리포지토리 이름을 지정하면 됩니다. 깃헙 액션 workflow file에서 해당 환경 변수를 사용하고 해당 이름으로 ECR 레포가 생성됩니다.
5. 깃허브 액션 workflow 파일 생성
그럼 이제 사전 준비가 완료되었으므로 실제 깃허브 액션파일을 만들겠습니다. 먼저 액션 파일의 위치는 아래와 같이 .github/workflows/ 디렉토리에 .yml(또는 yaml) 형식으로 저장합니다.
이미지 build & push 과정을 진행하는 액션 파일 예시입니다. (깃허브 링크)
name: Docker Build and Push on: push: tags: - '*' # 모든 태그에 대해 이 워크플로우를 트리거합니다. jobs: build-and-push: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ vars.AWS_REGION }} role-to-assume: arn:aws:iam::516696002612:role/github-actions-ci - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Extract tag name id: extract_tag shell: bash run: echo "IMAGE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - name: echo image tag run: echo $IMAGE_TAG - name: Build and Push Docker Image uses: docker/build-push-action@v5 with: push: true tags: ${{ steps.login-ecr.outputs.registry }}/${{ vars.AWS_ECR_REPO }}:${{ env.IMAGE_TAG }}
- on.push.tags. ‘*’
이 워크플로우는 GitHub 리포지토리에 태그가 푸시될 때마다 트리거됩니다. 여기서 ‘ * ‘는 모든 태그에 대해 이 워크플로우를 실행하겠다는 것을 의미합니다. 코드를 수정하고 태그가 푸시되면 workflow 파일이 실행합니다. 명시적으로 tag를 연결한 경우에만 workflow 작업을 실행하도록 main에 merge 하는 경우 등은 workflow 작업이 실행하지 않습니다. - jobs.build-and-push.runs-on: ubuntu-latest
이 작업은 ubuntu-latest 환경에서 실행되며, Docker 이미지를 빌드하고 Amazon ECR로 푸시하는 단계를 포함합니다. - permissions.id-token, contents
id-token: write와 contents: read 권한을 설정하여, GitHub Actions가 필요한 권한으로 ID 토큰을 작성하고 리포지토리 컨텐츠를 읽을 수 있도록 합니다.
- steps
개별 액션을 정의합니다.
name: Checkout Repository
actions/checkout@v4 액션을 사용하여 리포지토리의 소스 코드를 체크아웃합니다. 깃허브 액션에 사용할 수 있는 다양한 액션 리스트는 아래 github.com/actions 에서 확인할 수 있습니다.
- name: Configure AWS credentials
aws-actions/configure-aws-credentials@v4 액션을 사용하여 AWS 자격 증명을 구성합니다. 여기서 AWS 리전과 GitHub Actions가 사용할 IAM 역할의 ARN을 지정합니다. (vars.AWS_REGION과 role-to-assume 값 사용) 앞에서 생성한 IAM Role 정보를 사용합니다. - name: Login to Amazon ECR
aws-actions/amazon-ecr-login@v2 액션을 사용하여 Amazon ECR에 로그인합니다. 이 단계는 Docker 이미지를 Amazon ECR로 푸시할 때 필요한 인증 정보를 가져옵니다.
id: login-ecr 구문에서 login-ecr는 이 단계의 고유 식별자(ID)입니다. 이 ID를 사용하여, 해당 단계의 출력(output)을 다른 단계에서 참조할 수 있습니다. 예를 들어, 이미지를 push하는 steps.login-ecr.outputs.some-output과 같은 방식으로 사용합니다.
- name: Extract tag name
자동으로 생성되는 해시 값을 기준으로 도커 이미지 태그로 사용하는 다른 액션 파일과 다른 부분입니다. 깃허브의 태그 정보를 환경 변수를 저장하여 이를 이미지 태그 정보를 사용하는 설정입니다. Bash 스크립트를 사용하여 태그 이름을 추출하고, 이를 환경 변수 IMAGE_TAG에 저장합니다. 이 변수는 Docker 이미지의 태그로 사용됩니다. - name: echo image tag
디버깅 용도로 이미지 태그 정보를 출력하는 설정입니다. - name: Build and Push Docker Image
docker/build-push-action@v5 액션을 사용하여 Docker 이미지를 빌드하고 푸시합니다. push: true는 이미지를 레지스트리로 실제로 푸시해야 함을 의미하며, tags는 이미지에 사용될 태그를 지정합니다. 여기서 Amazon ECR의 레지스트리 주소, ECR 리포지토리 이름, 그리고 앞서 추출한 이미지 태그를 사용하였습니다.
앞에서 깃허브에 설정한 환경 변수를 ECR 리포지토리 이름으로 지정하였습니다.
이제 깃허브 액션 파일 설정이 완료되었습니다. 다음에는 태그를 Push 하여 실제 깃허브 액션 workflow를 실행하겠습니다.
6. 이미지 build & push 깃허브 액션 작업 검증
그럼 로컬에서 새로운 태그를 생성하고 깃허브에 해당 태그를 Push 해서 액션 워크플로우 파일을 실행합니다.
새로운 태그를 만듭니다.
(jerry-dev:sns)github-actions-python$ git tag -a v0.1.3
해당 태그를 원격 origin에 push 합니다.
(jerry-dev:sns)github-actions-python$ git push --tags Enumerating objects: 1, done. Counting objects: 100% (1/1), done. Writing objects: 100% (1/1), 153 bytes | 153.00 KiB/s, done. Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/junghoon2/github-actions-python.git * [new tag] v0.1.3 -> v0.1.3
이제 원격 깃허브의 ‘Actions’ 메뉴를 확인하면 아래와 같이 자동으로 Jobs 실행되고 있습니다.
해당 메뉴를 클릭하면 상세한 작업 로그를 확인할 수 있습니다.
에러없이 작업이 완료되면 아래와 같이 체크 표시를 확인할 수 있습니다.
이제 작업이 완료되었으므로 작업 결과로 AWS ECR 콘솔을 확인하면 액션파일 설정대로 깃허브 태그 이름으로 이미지 태그가 지정되어 이미지가 Push 되어 있는 것을 확인할 수 있습니다.
7. 생성한 ECR 이미지 기반 파드 생성(이미지 검증)
그럼 마지막 단계로 생성된 이미지 주소를 파드의 매니페스트에 명시하여 정상으로 이미지가 생성되었는지 검증하겠습니다. 기본 deployment 매니페스트를 사용하고 이미지 부분만 방금 생성한 ECR 이미지 주소와 태그로 변경합니다.
apiVersion: apps/v1 kind: Deployment metadata: name: python-flask namespace: default spec: selector: matchLabels: app: python-flask template: metadata: labels: app: python-flask spec: containers: - name: python-flask image: 516696002612.dkr.ecr.ap-northeast-2.amazonaws.com/python-flask:v0.1.3
image
: 각자 생성한 이미지 주소로 변경합니다. 이미지 주소는 깃허브 액션 파일 혹은 아래와 같이 ECR 콘솔에서 참조할 수 있습니다.
YAML 파일을 적용(apply)하고 포트포워드를 이용하여 접속합니다.
$ k apply -f deployment/deploy-ecr-python-flask.yaml deployment.apps/python-flask created $ k port-forward pod/python-flask-6f75c79c8-pdpnh 8080:8080 Forwarding from 127.0.0.1:8080 -> 8080 Forwarding from [::1]:8080 -> 8080
정상으로 파드가 실행되고 브라우저에서 이미지 내용도 변경된 것을 확인할 수 있습니다.
이제 설정한 깃허브 액션 파일을 이용하여 조직에서는 깃허브에 태그가 Push 되면 자동으로 이미지를 빌드하고 원격 ECR에 Push까지 하는 CI 시스템을 구성 완료 하였습니다. CI 시스템은 한번만 설정하면 이후 변경할일이 별로 없는 작업이기도 합니다.
이상으로 깃허브 액션을 이용하여 도커 이미지 Build, Push까지 하는 CI 시스템을 구성해 보았습니다.
해당 기술 블로그에 질문이 있으시면 언제든지 문의해 주세요. 직접 답변해 드립니다.
k8sqna@jennifersoft.com