Github Actions는 Github에서 제공하는 CI / CD를 위한 툴이다.
- CI : Continous Integration(지속적 통합)
- CD: Continous Delivery or Continous Deploy(지속적 배포)
사용자의 피드백을 빠르게 적용하기 위해서 많은 수의 배포가 빠르게 진행되어야 한다. 이는 곧 서비스의 경쟁력이 되기 때문이다. 한 번의 코드 변화로부터 사용자에게 전달되기까지 모든 과정이 수동으로 이루어진다면 많은 시간이 걸릴 것이다. CI / CD는 그 과정을 자동화하는 것이라고 생각한다.
쉽게 예를 들어 CI / CD를 구분해보자.
- CI : 소스코드의 변경에 따른 도커 이미지의 빌드 및 푸시
- CD : 변경된 도커 이미지를 바탕으로 쿠버네티스의 리소스 업데이트
위 그림은 Source Code의 변경에 따른 도커 빌드 및 푸시 그리고 Helm을 통한 배포를 나타내고 있다. 이 모든 과정이 Github Actions를 통해 이루어진다.
Hello World
Github Actions의 Hello World는 하나의 Workflow를 보는 것이다.
Workflow란 하나 또는 여러 개의 Job으로 구성된 프로세스를 이야기한다. Yaml 파일로 작성해야 한다.
먼저 하나의 예시를 보자.
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run a one-line script
run: echo Hello, world!
- name: Run a multi-line script
run: |
echo Add other actions to build,
echo test, and deploy your project.
하나의 Yaml 파일은 하나의 Workflow를 이야기한다.
위 파일에서 Workflow의 이름은 CI이고 Push라는 이벤트에 의해 Workflow가 실행된다. 그리고 1개의 build라는 Job으로 이루어져 있고 build라는 Job은 ubuntu-latest라는 환경에서 실행되고 3개의 Step으로 구성되어 있다. 첫 번째 step은 [actions/checkout@v3](<https://github.com/actions/checkout>) 에 정의된 일을 하고 두 번째 step은 run에 명시된 바와 같이 Hello, world! 를 화면에 출력한다. 마지막으로 세 번째 step은 두 번의 echo 명령어를 수행한다.
글로 적은 설명이 더 자세하지만 간단하게 그림으로도 나타내 보았다.
CI: Continous Integration (지속적 통합)
지속적 통합에서 훨씬 더 다양한 Task가 있을 수 있지만 여기서 나는 도커 빌드라는 Task와 도커 푸시라는 Task만을 지속적 통합으로 수행하려 한다. 생성된 도커 이미지를 원격 저장소에 업로드하지 않으면 만든 도커 이미지는 단지 Github Actions의 Workflow 내부에만 존재하는 것이다. 이 포스팅에선 Docker Hub라는 도커 이미지를 위한 원격 저장소에 업로드할 것이다.
도커로 이미지를 만드는 이유는 CI / CD를 하는 이유과 같다. 반복을 피하기 위해서다. 한번 도커 이미지를 만들어놓으면 도커 엔진이 설치되어 있는 어떠한 환경이든 손쉽게 애플리케이션을 배포할 수 있다. 그리고 가장 중요한 이유로 쿠버네티스에서 배포를 위해선 도커 이미지가 필요하다… (가장 현실적인 이유가 아닐까 싶다.. 🤣)
도커 빌드
도커 빌드를 위해선 docker build라는 명령어를 수행해야 한다. 이 명령어를 우리의 컴퓨터에서 실행하는 게 아니다. Github Actions가 빌려주는 Workflow 안에서 실행할 것이다. 그 환경을 Runner라고 부른다. Hello World에서 했던 Runner는 ubuntu-latest라는 OS에서 실행된다. Github Actions는 Windows, Macos 등의 환경을 제공하는 데 여기서 상세히 확인할 수 있다.
우리는 ubuntu-latest에서 docker build 명령어를 수행할 건데 기본적으로 도커가 설치되어 있기 때문에 아래와 같이 손쉽게 도커 빌드를 수행할 수 있다.
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Docker build
run: docker build -t ssuwani/flask_app:${{ github.sha }} .
도커 이미지 이름의 prefix에는 Docker Hub 계정의 username을 작성해야 한다. 도커 이미지의 태그로 작성한 ${{ github.sha }} 는 Workflow를 트리거 한 commit의 SHA 값이다. 자동으로 붙는 latest 태그 만으로는 이미지의 버전 관리가 어렵기 때문에 태그를 작성했다.
도커 푸시
앞서 만든 도커 이미지를 Kubernetes 클러스터에서 가져올 수 있어야 한다. 그러기 위해선 만든 이미지가 Runner 내부가 아닌 원격 저장소에 위치해야 한다. 원격 저장소다 보니 당연히 권한이 있어야 접근이 가능하다. Github에서 제공하는 Github Package를 사용한다면 Github Actions에선 조금 더 손쉽게 접근할 수 있지만 Docker Hub가 조금 더 범용적이기도 하고 Github Packages는 기본적으로 Private Repository 다 보니 설정이 귀찮아서 기본적으로 Public Repository인 도커 허브에 업로드하고자 한다.
푸시에 앞서 도커 허브에 로그인하는 과정이 필요하다.
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Docker build
run: docker build -t ssuwani/flask_app:${{ github.sha }} .
- name: Login Docker hub
run: docker login -u ssuwani -p ${{ secrets.DOCKER_PASSWORD }}
위에 작성한 바와 같이 docker login 명령어로 도커 허브에 로그인할 수 있는데 지금 작성 중인 Workflow Yaml 파일은 Github 저장소에 업로드되어야 하는 파일이다. 도커 허브에 접속을 위한 비밀번호가 직접 노출되어서 안된다. 이때 사용할 수 있는 것이 Github Actions를 위한 Secret이다.
레포지토리의 Settings → Secrets → Actions → New repository secret를 클릭하면 된다.
- Name: DOCKER_PASSWORD
- Value:
이렇게 Secret을 등록하고 나면 ${{ secrets.DOCKER_PASSWORD }} 와 같이 보안정보를 불러올 수 있다.
이제 도커 푸시하는 명령어까지 추가한 Workflow 파일이다.
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Docker build
run: docker build -t ssuwani/flask_app:${{ github.sha }} .
- name: Login Docker hub
run: docker login -u ssuwani -p ${{ secrets.DOCKER_PASSWORD }}
- name: Docker push
run: docker push ssuwani/flask_app
도커 푸시는 위와 같이 docker push 라는 명령어로 할 수 있다.
이야기했다시피 ssuwani 이 부분은 docker hub의 username을 입력해야 한다.
그리고 여기서는 이미지의 태그를 입력하지 않았는데, 태그가 없으면 기본적으로 생성되어 있는 이미지의 모든 태그를 푸시한다. 여기서는 ${{ github.sha }} 밖에 없다. 그래서 생략했다. (가끔 이게 유용할 때가 있다)
이제 원격 저장소인 Docker hub에 나의 이미지가 업로드되었다. 여기까지가 CI를 완료한 것이다. 이제 CD를 해보자.
CD: Continous Deploy (지속적 배포)
CI 과정에서의 산출물인 도커 이미지를 통해 쿠버네티스에 새로운 배포가 진행되어야 한다. 새로운 배포라는 것은 새로운 도커 이미지의 태그를 전달함으로써 수행할 수 있다. (앞전에 설명했지만 latest 태그를 통해 태그에 구애받지 않고 지속적 배포를 수행할 수 있지만 롤백 등을 위한 버전 관리의 어려움이 있어서 태그를 전달하고자 한다.)
그러면 쿠버네티스에 배포를 위해선 어떤 툴을 사용할 수 있을까? 대표적으로 아래 3가지가 있다.
- kubectl
- kustomize
- helm
위 3가지 모두 이 시나리오에서 사용할 수 있지만 kubectl로 가장 간단하게 이미지를 업데이트하고자 한다. 바로 아래 명령어다.
kubectl set image deployment/flask-deployment flask-app=flask_app:${{ github.sha }}
이 명령어는 당연히 먼저 deployment가 배포되어 있는 것을 가정하고 사용하는 명령어다.
이미지 업데이트는 간단하게 할 수 있지만 인증에 관한 부분이 조금 까다롭다. 현재 CD는 Github Actions의 Workflow에서 제공하는 Runner인 Ubuntu 환경이다. 어떤 쿠버네티스 클러스터를 사용하든 간에 이 환경 안에는 없다. 따라서 클러스터의 정보를 받아올 수 있어야 한다. 클러스터와 통신할 수 있어야 한다.
나는 여기서 GKE를 사용하고자 한다. 구글에서 서비스하는 Kubernetes Engine이다. 클러스터 구축을 위해선 무료로 쿠버네티스 쉽게 계속 사용하기(GKE)에서 소개했다.
위 포스팅을 따라 클러스터를 생성했고 정보는 다음과 같다.
- cluster-name: cluster-1
- location: asia-northeast3-a
그리고 GKE에 인증을 하기 위해선 아래 workflow에 아래 Step을 추가하면 된다. 아래 내용은 여기의 리스트에서 Build and Deploy to GKE에서 가져왔다. 기존에 공개된 템플릿들이 많이 존재하니까 참고하면 된다.
- id: 'auth'
uses: 'google-github-actions/auth@v0'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: Set up GKE credentials
uses: google-github-actions/get-gke-credentials@v0
with:
cluster_name: cluster-1
location: asia-northeast3-a
여기서 ${{ secrets.GCP_CREDENTIALS }} 가 있는데 이는 서비스 계정의 키를 발급받아 DOCKER_PASSWORD를 입력했던 것과 같이 Github 레포지토리의 Secret에 지정해줘야 한다.
서비스 계정의 키 발급은 아래의 과정을 거치면 된다.
- IAM 및 관리자 → 서비스 계정 → 서비스 계정 만들기
- 서비스 계정 이름: admin 서비스 계정 ID: admin-sa 만들고 계속하기
- 이 서비스 계정에 프로젝트에 대한 액세스 권한 부여 역할 → 기본 → 소유자(정말 정말 주의해야 한다. 어딘가에 올리면 안 된다)
- 완료
- 생성된 서비스 계정 클릭
- 키 → 키 추가 → 새 키 만들기
그러면 JSON 파일을 다운로드할 수 있다. 이 파일의 내용을 Github내 Secret으로 입력해주면 된다.
절 대 절 대 Key는 Github 레포지토리의 Secret이 아닌 곳에 업로드하면 안 된다..
그리고 방금 만든 키는 소유자 권한을 갖는 서비스 계정이다. 서비스 계정은 사용자가 아닌 애플리케이션에 권한을 부여하기 위함인데 방금 만든 키를 갖고 있는 누군가는 소유자 권한을 갖는 계정을 갖는 것과 마찬가지다. 정말 정말 주의해야 한다……. 나는 책임을 지지 않는다. 조심하자!!!!!!!!!
이야기했다시피 ssuwani 이 부분은 docker hub의 username을 입력해야 한다.
그리고 여기서는 이미지의 태그를 입력하지 않았는데, 태그가 없으면 기본적으로 생성되어 있는 이미지의 모든 태그를 푸시한다. 여기서는 ${{ github.sha }} 밖에 없다. 그래서 생략했다. (가끔 이게 유용할 때가 있다)
이제 원격 저장소인 Docker hub에 나의 이미지가 업로드되었다. 여기 까지가 CI를 완료한 것이다. 이제 CD를 해보자.
CD: Continous Deploy (지속적 배포)
CI 과정에서의 산출물인 도커 이미지를 통해 쿠버네티스에 새로운 배포가 진행되어야 한다. 새로운 배포라는 것은 새로운 도커 이미지의 태그를 전달함으로써 수행할 수 있다. (앞전에 설명했지만 latest 태그를 통해 태그에 구애받지 않고 지속적 배포를 수행할 수 있지만 롤백 등을 위한 버전 관리의 어려움이 있어서 태그를 전달하고자 한다.)
그러면 쿠버네티스에 배포를 위해선 어떤 툴을 사용할 수 있을까? 대표적으로 아래 3가지가 있다.
- kubectl
- kustomize
- helm
위 3가지 모두 이 시나리오에서 사용할 수 있지만 kubectl로 가장 간단하게 이미지를 업데이트하고자 한다. 바로 아래 명령어다.
kubectl set image deployment/flask-deployment flask-app=flask_app:${{ github.sha }}
이 명령어는 당연히 먼저 deployment가 배포되어 있는 것을 가정하고 사용하는 명령어다.
이미지 업데이트는 간단하게 할 수 있지만 인증에 관한 부분이 조금 까다롭다. 현재 CD는 Github Actions의 Workflow에서 제공하는 Runner인 Ubuntu 환경이다. 어떤 쿠버네티스 클러스터를 사용하든 간에 이 환경 안에는 없다. 따라서 클러스터의 정보를 받아올 수 있어야 한다. 클러스터와 통신할 수 있어야 한다.
나는 여기서 GKE를 사용하고자 한다. 구글에서 서비스하는 Kubernetes Engine이다. 클러스터 구축을 위해선 무료로 쿠버네티스 쉽게 계속 사용하기(GKE)에서 소개했다.
위 포스팅을 따라 클러스터를 생성했고 정보는 다음과 같다.
- cluster-name: cluster-1
- location: asia-northeast3-a
그리고 GKE에 인증을 하기 위해선 아래 workflow에 아래 Step을 추가하면 된다. 아래 내용은 여기의 리스트에서 Build and Deploy to GKE에서 ********가져왔다. 기존에 공개된 템플릿들이 많이 존재하니까 참고하면 된다.
- id: 'auth'
uses: 'google-github-actions/auth@v0'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: Set up GKE credentials
uses: google-github-actions/get-gke-credentials@v0
with:
cluster_name: cluster-1
location: asia-northeast3-a
여기서 ${{ secrets.GCP_CREDENTIALS }} 가 있는데 이는 서비스 계정의 키를 발급받아 DOCKER_PASSWORD를 입력했던 것과 같이 Github 레포지토리의 Secret에 지정해줘야 한다.
서비스 계정의 키 발급은 아래의 과정을 거치면 된다.
- IAM 및 관리자 → 서비스 계정 → 서비스 계정 만들기
- 서비스 계정 이름: admin 서비스 계정 ID: admin-sa 만들고 계속하기
- 이 서비스 계정에 프로젝트에 대한 액세스 권한 부여 역할 → 기본 → 소유자(정말 정말 주의해야 한다. 어딘가에 올리면 안 된다)
- 완료
- 생성된 서비스 계정 클릭
- 키 → 키 추가 → 새 키 만들기
그러면 JSON 파일을 다운로드할 수 있다. 이 파일의 내용을 Github 내 Secret으로 입력해주면 된다.
절 대 절 대 Key는 Github 레포지토리의 Secret이 아닌 곳에 업로드하면 안 된다..
그리고 방금 만든 키는 소유자 권한을 갖는 서비스 계정이다. 서비스 계정은 사용자가 아닌 애플리케이션에 권한을 부여하기 위함인데 방금 만든 키를 갖고 있는 누군가는 소유자 권한을 갖는 계정을 갖는 것과 마찬가지다. 정말 정말 주의해야 한다……. 나는 책임을 지지 않는다. 조심하자!!!!!!!!!