1. 개요
Jenkins나 Tekton 같은 별도의 CI 도구만 사용해 왔고, Github Actions나 GitLab CI/CD와 같은 레포지토리 기반의 통합 CICD 도구는 존재만 알고 있었다. 이번 스터디를 통해 Github Actions 사용법에 간단히 알아보고, 이를 활용하여 CI/CD 파이프라인을 구성하는 방법을 살펴본다.
GitHub Actions는 GitHub 사용자들에게 직관적이고 통합적인 CI/CD 환경을 제공하며, Github 페이지에서 Actions 탭을 통해 제공하기 때문에 GitHub Actions를 사용하면 코드 관리와 자동화를 한 플랫폼에서 수행할 수 있어, 팀 협업과 효율적인 소프트웨어 개발 프로세스를 구현할 수 있다.
2. 주요 컴포넌트
GitHub Actions는 아래와 같은 주요 컴포넌트로 구성된다.
워크플로우(Workflow)
- Github Actions에서 실행되는 자동화 작업의 집합( Jenkinsfile과 비슷 )
- 레포지토리의. github/workflows YAML파일로 정의
이벤트(Event)
- 워크플로우를 트리거하는 Github 이벤트
- push, pull_request, schedule, workflow_dispatch 등이 있음
잡(Job)
- 워크폴로우 내에서 실행 가능한 가장 작은 작업 단위
- 각 잡은 독립적으로 실행되고, 필요하면 의존성을 가질 수 있음
단계(Step)
- 잡 내부에서 순차적으로 실행되는 명령
- 스크립트 명령어나 미리 정의된 액션으로 구성
액션(Action)
- Github Actions에서 실행 가능한 재사용 가능한 작업단위.
- 커스텀 액션을 작성하거나 마켓 플레이스에서 다운로드 가능
3. Github Actions 실행 환경
Github Actions의 워크플로우를 실행하는 다양한 방법에 대해 알아보자.
3.1 Gihub-hosted Runner : Github 서버에서 실행
GitHub-hosted Runner는 Github에서 제공하는 클라우드 기반 실행 환경으로, GitHub Actions 워크플로우를 실행하기 위한 컴퓨팅 리소스를 제공한다. 사용자는 별도의 인프라를 준비할 필요 없이, GitHub에서 관리하는 서버에서 자동으로 워크플로우를 실행할 수 있다.
runs-on:
필드 값에 실행시킬 runner를 지정한다.Linux, MacOS, Window
등 다양한 OS환경을 제공한다.
name: Build and Test
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest # GitHub-hosted Runner
# windows-latest
# macos-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
3.2 Self-hosted runner : 사용자 서버에서 실행
Self-hosted Runner는 사용자가 직접 호스팅 하고 관리하는 실행 환경으로, 사용자가 Github Actions 워크플로우를 실행하기 위한 컴퓨팅 리소스를 연결해야 한다. GitHub-hosted Runner와 달리, 사용자 환경에 맞게 커스터마이징 가능하며, GitHub에서 제공하지 않는 특수 환경(폐쇄망 등)이나 종속성을 지원할 수 있다.
Runner 설치
Repository > Settings < Runner : github 메뉴에서 runner를 생성한다. OS를 선택하면 환경에 맞는 설치 스크립트를 제공한다.
스크립트 실행
- 레포지토리 메뉴에서 제공하는 token 값에 주의한다.
- 제공하는 스크립트는 기본적으로 root 계정을 허용하지 않아, root 계정을 사용할 경우 아래 환경변수를 추가
# 작업폴더 생성
$ mkdir actions-runner && cd actions-runner
# 다운로드
$ curl -o actions-runner-osx-x64-2.321.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-osx-x64-2.321.0.tar.gz
# Optional: Validate the hash
$ echo "b2c91416b3e4d579ae69fc2c381fc50dbda13f1b3fcc283187e2c75d1b173072 actions-runner-osx-x64-2.321.0.tar.gz" | shasum -a 256 -c
# 압축해제
$ tar xzf ./actions-runner-osx-x64-2.321.0.tar.gz
# 설정파일
$ ./config.sh --url https://github.com/hackjap/cicd --token <TOEKN_VALUE>
# 스크립트 실행시 sudo 에러가 발생하면 아래 변수 추가
export RUNNER_ALLOW_RUNASROOT="1"
# Last step, run it!
$ ./run.sh
연결 시 성공 시 아래와 같이 확인할 수 있다.
이제 workflow파일에서 사용하려면 아래와 같이, runner 생성 시 지정한 label 명을 넣어주면 된다.
jobs:
build:
runs-on: self-hosted
3.3 Act: 로컬 환경에서 Github Action 실행
act는 GitHub Actions와 완전히 동일한 환경을 제공하지는 않지만, 로컬에서 빠르게 워크플로우를 테스트하고 디버깅할 수 있는 CLI 도구이다. Docker 컨테이너로 동작하기 때문에, Docker 환경이 구성되어 있어야 한다. 다만, act는 로컬에서 실행되지 않는 특정 GitHub Actions 기능이 있을 수 있으니, 프로덕션 환경에서는 실제 GitHub-hosted Runner에서 추가 테스트를 수행하는 것이 필요하다.
act 설치(MacOS)
brew를 통해 act를 설치한다.
brew install act
# 워크플로우 확인
act -l --container-architecture linux/amd64
INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'
Stage Job ID Job name Workflow name Workflow file Events
0 run-playbooks run-playbooks Run Ansible ansible.yml workflow_dispatch,push
0 build build Hello World hello-world.yml workflow_dispatch,push
로컬에서 실행하기 때문에, GIthub에서 제공하는 Variables & Secrets를 사용하지 못하고, 직접 변수를 등록해주어야 한다. 시크릿으로 사용할 변수를 정의하는 파일을 생성한다.
# 시크릿 파일 생성
cat << EOF > my.secrets
MY_SECRET=VALUE
EOF
등록한 시크릿을 출력하는 간단한 워크플로우 파일을 작성한다.
cat << EOF > .github/workflows/hello-world.yml
name: Hello World # 워크플로우
on: # Events
workflow_dispatch:
push:
jobs: # Jobs
build:
runs-on: ubuntu-latest
steps: # Step
- uses: actions/checkout@v3 # Action
- name: Hello World
run: |
echo "Hello World! Github Actions 🚀"
echo ENV: ${{ envs.MY_ENV }}
echo SECRET: ${{ secrets.MY_SECRET }}
EOF
워크플로우 실행
- act 명령어만 입력할 경우, 모든 워크플로우 파일이 실행되고 -W 옵션을 통해 특정 워크플로우파일만 지정할 수 있음
- env와 secrets은 --env, --env-files 옵션 모두를 제공하여 파일 혹은 명령어로 사용이 가능
4. 워크플로우 기본 사용
변수를 설정하고 출력하는 간단한 워크플로우 파일을 작성해보고 실행해보자.
4.1 변수 설정(Variables & Secrets)
먼저, 워크플로우 파일에서 사용할 변수를 등록하자. 자주 사용하는 변수나 민감한 정보를 Github에서 제공하는 Variables & Secrets 기능을 통해 관리할 수 있다.
변수 등록 : Repostry > Settings > Secrets and variables > action 메뉴에서 variables를 설정한다.
Action로 배포할 대상 서버 IP를 등록한다.
서버의 개인키는 민감정보로 시크릿을 통해 등록한다.
변수 기본 사용 방법 :
- 변수 정의는
vars
,secrets
키워드 사용하여 설정한다. - action 사용 시에는
vars
,secrets
키워드 사용 - 앤서블 문법과 유사하게
{{ 변수명 }}
안에 변수명을 기입하여 참조한다.
그럼 간단하게 변수와 시크릿을 출력하는 워크플로우 파일을 작성해 보고 실행하여 확인해 보자.
MESSAGE라는
Variables를 등록한다.
APP_SECRET라는
Secrets을 등록한다.
워크플로우 파일을 생성하고 이를 깃 푸시합니다.
# 워크플로우 파일 생성
cat << EOF > .github/workflows/hello-world.yml
name: Hello World # 워크플로우
on: # Events
workflow_dispatch:
push:
jobs: # Jobs
build:
runs-on: ubuntu-latest
steps: # Step
- uses: actions/checkout@v3 # Action
- name: Hello World
run: |
echo "Hello World! Github Actions 🚀"
echo ENV: ${{ vars.MESSAGE }}
echo SECRET: ${{ secrets.APP_SECRET }}
EOF
# 파일 확인
ls -al .github/workflows/hello-world.yml
-rw-r--r--@ 1 spjang staff 374 12 13 14:29 .github/workflows/hello-world.yml
# 깃 푸시
git add -A && git commit -m 'update' && git push
실행 결과를 확인하면, 설정한 변수가 정상적으로 출력되는 것을 확인할 수 있다.
이후 실습부터는 각 사용자가 정의한 변수가 이미 설정되어 있다는 가정하에 진행한다.
5. 다양한 워크플로우 구성하기
다양한 케이스의 워크플로우를 구성해보자.
5.1 Case 1: SSH로 원격 대상에서 명령어 실행
먼저 간단하게 SSH 명령어를 사용해 타겟 서버에 접속하고 명령어를 실행하는 워크플로우를 작성해 보자.
- SSH 대상 서버 정보를 secret으로 등록
- 예: `SSH_HOST, SSH_USER, SSH_PASSWORD
- ssh-actions 플러그인 사용
- 마켓플레이스에서 제공하는 appleboy/ssh-action을 활용해 SSH 명령어를 실행한다.
name: remote ssh command
on: [push]
jobs:
build:
name: Build
runs-on: self-hosted
steps:
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.HOST }}
username: root
password: ${{ secrets.PASSWORD }}
port: ${{ secrets.PORT }}
script: |
whoami
ls -al
“푸시를 완료한 후, Actions 탭에서 워크플로우 실행 결과를 확인한다.
이 과정에서 appleboy/ssh-action@v1.2.0 플러그인을 사용해 원격 대상지로 명령어를 성공적으로 실행하고, 결과를 가져오는 것을 확인할 수 있다. 이를 활용하면 배포에 필요한 명령어나 다양한 설정을 자동화할 수 있다.”
5.2 Case 2: Ansible을 활용
Ansible을 사용하여 원격 명령어를 더 우아하고 효율적으로 실행해 보자. 자동화를 위해 Ansible 플레이북을 작성하고, 이를 통해 대상 서버에 요청을 전달한다. 즉, 러너의 호스트가 Ansible 서버 역할을 하며 명령어를 실행하는 주체라고 생각하면 된다. Nginx 웹서버를 간단히 설치하고 실행하는 Ansible 플레이북을 작성한 뒤, 해당 플레이북을 실행하는 GitHub Actions 워크플로우를 작성해 보자.
- push 워크플로우가 코드 변경 사항이 레포지토리에 푸시될 때 자동으로 실행
- Ansible 인벤토리와 nginx 웹 서버를 배포하는 간단한 플레이북을 작성
- nginx 설치 및 배포확인
# ./github/workflows/ansible.yml
name: Run Ansible
on:
workflow_dispatch:
push:
branches:
- main
jobs:
run-playbooks:
runs-on: self-hosted
steps:
- name: Github Repository Checkout
uses: actions/checkout@v4
- name: Setup Python 3
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Upgrade Pip & Install Ansible
run: |
python -m pip install --upgrade pip
python -m pip install ansible
- name: Implement the Private SSH Key
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Ansible Inventory File for Remote host
run: |
mkdir -p ./devops/ansible/
export INVENTORY_FILE=./devops/ansible/inventory.ini
echo "[my_host_group]" > $INVENTORY_FILE
echo "${{ secrets.TARGET_SERVER_IP }}" >> $INVENTORY_FILE
- name: Ansible Default Configuration File
run: |
pwd && ls -al
mkdir -p ./devops/ansible/
cat <<EOF > ./devops/ansible/ansible.cfg
[defaults]
ansible_python_interpreter = '/usr/bin/python3'
ansible_ssh_private_key_file = ~/.ssh/id_rsa
remote_user = root
inventory = ./inventory.ini
host_key_checking = False
EOF
- name: Write Playbook
run: |
cat << EOF > ./devops/ansible/install-nginx.yaml
---
- name: Install and Configure Nginx
hosts: all
become: yes # root 권한으로 실행
tasks:
- name: Update apt cache
apt:
update_cache: yes
- name: Install Nginx
apt:
name: nginx
state: present
- name: Start and Enable Nginx
service:
name: nginx
state: started
enabled: yes
- name: Check Nginx status
shell: curl -I http://localhost
register: nginx_status
- name: Print Nginx status
debug:
msg: "{{ nginx_status.stdout }}"
EOF
- name: Ping Ansible Hosts
working-directory: ./devops/ansible/
run: |
ansible all -m ping
- name: Run Ansible Playbooks
working-directory: ./devops/ansible/
run: |
ansible-playbook install-nginx.yaml
# - name: Deploy Python via Ansible
# working-directory: ./devops/ansible/
# run: |
# ansible-playbook deploy-python.yaml
실행 결과를 확인해 보면 플레이북이 정상적으로 실행되었으며, Nginx 웹 서버 접속이 성공적으로 이루어지는 것을 확인할 수 있다.
GitHub Actions를 Ansible과 같은 선언형 관리 도구와 연계하면, 서버 프로비저닝, 애플리케이션 배포, 구성 관리와 같은 작업을 자동화하고, CI/CD 파이프라인에 통합할 수 있다.예를 들어, Actions에서 Ansible Playbook을 실행하여 서버 환경을 설정하거나, 애플리케이션의 상태를 점검하고 업데이트하는 워크플로우를 손쉽게 구현할 수 있다
5.3 case 3 : CICD 파이프라인 ( Git + Docker + ArgoCD )
Docker를 이용해 이미지를 빌드하고, 이를 레지스트리에 푸시한 뒤, ArgoCD를 통해 배포하는 GitHub Actions Workflow를 작성하여 GitOps CI/CD 파이프라인을 구성해보자.
- build : Docker 이미지 빌드 및 푸시
- 코드 변경사항이 main 브랜치로 푸시되면 워크플로우가 트리거
- Docker 이미지를 빌드하고, 커밋 해시를 태그로 사용해 Docker Registry에 푸시
{{ github.sha }}
는 깃허브에서 기본적으로 제공하는 환경 변수로, 커밋 해시 값임
- deploy: Kubernetes 매니페스트 업데이트
- yq를 사용해 쿠버네티스 메니페스트 파일의 이미지 태그를 새로 빌드된 Docker 이미지 태그로 업데이트
- 업데이트된 매니페스트를 Git 레포지토리에 커밋 & 푸시
- ArgoCD를 사용해 쿠버네티스 클러스터로 자동배포(Auto Sync를 사용하지 않는다면 주석 스텝 해제)
name: CI/CD Pipeline with Docker and GitOps # 더 명확한 워크플로우 이름
on:
push:
branches:
- main
jobs:
build:
name: Build and Push Docker Image # Docker 이미지 빌드/푸시 작업
runs-on: self-hosted
steps:
# 소스코드 체크아웃
- name: Checkout Source Code
uses: actions/checkout@v3
# Docker 레지스트리 로그인
- name: Authenticate with Docker Registry
run: docker login ${{ secrets.DOCKER_REGISTRY_URL }} -u ${{ secrets.DOCKER_REGISTRY_USER }} -p${{ secrets.DOCKER_REGISTRY_PASSWORD }}
# Docker 이미지 빌드 - commit hash를 태그로 사용
- name: Build Docker Image
run: docker build -t ${{ secrets.DOCKER_REGISTRY_URL }}/${{ secrets.DOCKER_IMAGE_NAME }}:${{ github.sha }} .
# 빌드된 이미지를 레지스트리에 푸시
- name: Push to Registry
run: docker push ${{ secrets.DOCKER_REGISTRY_URL }}/${{ secrets.DOCKER_IMAGE_NAME }}:${{ github.sha }}
deploy:
name: Update Kubernetes Manifest # GitOps를 위한 매니페스트 업데이트 작업
runs-on: self-hosted
needs: build # build 작업 완료 후 실행
steps:
# YAML 처리를 위한 yq 설치
- name: Install YAML processor
run: |
sudo apt-get update
sudo apt-get install -y yq
# 매니페스트의 이미지 태그 업데이트
- name: Update Deployment Manifest
run: |
yq e ".spec.template.spec.containers[0].image = \"${{ secrets.DOCKER_REGISTRY_URL }}/${{ secrets.DOCKER_IMAGE_NAME }}:${{ github.sha }}\"" -i deploy/nginx-deployment.yaml
# 변경사항 커밋 및 푸시
- name: Commit and Push Changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.email "devops@gmail.com"
git config --global user.name "devops"
git add deploy/*
git diff --cached --quiet || git commit -m "UPDATE :: Updated image to ${{ secrets.DOCKER_REGISTRY_URL }}/${{ secrets.DOCKER_IMAGE_NAME }}:${{ github.sha }}"
git push origin main
# ArgoCD 자동 동기화 설정 (선택사항 - Not Auto Sync)
# - name: Sync ArgoCD Application
# run: |
# argocd app sync my-app \
# --server ${{ secrets.ARGOCD_SERVER }} \
# --auth-token ${{ secrets.ARGOCD_AUTH_TOKEN }} \
# --revision ${GITHUB_SHA}
이제 작성한 내용을 푸시하고, 워크플로우 실행 상태를 확인해 보자. 하지만, 올바른 토큰으로 인증하여도, 아래와 같이 워크플로우 레포지토리로 push 하는 과정에서 권한 에러가 발생한다. 왜냐면 워크플로우가 레포지토리에 접근하기 위해서는 별도의 권한 설정이 필요하다.
이경우에는 Workflow permissions
메뉴에서 Read and write permissions
으로 설정해야 한다.
GitHub Actions 페이지에서 작성한 Job들이 정상적으로 수행되는 것을 확인할 수 있다.
이미지 레지스트리는 Harbor를 사용했으며, 설정한 Docker 이미지가 성공적으로 푸시된 것을 확인할 수 있다.
ArgoCD에서도 매니페스트 레포지토리의 변경 사항을 감지하고, 동기화(Sync)가 정상적으로 동작하는 것을 확인할 수 있다.
문제 발생
다음날 Github Actions 페이지를 확인해 보니 약 300개의 워크플로우가 실행되고 있었다. 원인을 파악해 보니, 워크플로우는 push 이벤트가 발생할 때 실행되도록 설정되어 있는데, 마지막 단계에서 매니페스트를 push하는 로직이 포함되어 있어, 무한 루프에 빠진 것이다. 실제 운영 환경이었다면 큰 문제가 발생할 뻔했다..
해결방법은 아래와 같이 메니페스트 파일을 무시하도록 설정하여, 워크플로우가 실행되지 않도록 할 수 있다. 그러나, 매니페스트 파일은 별도의 브랜치나 레포지토리에 관리하는 것을 추천한다.
on:
push:
branches:
- main
paths-ignore:
- "deloy/**" # manfiest 파일 경로: manifest 파일 변경은 트리거하지 않음
6. 결론
이번 포스팅에서는 GitHub Actions의 기본 사용 방법을 간단히 살펴보았다. 느낀 점은 워크플로우 파일을 YAML로 작성하니 Jenkins에서 사용하던 Groovy 문법에 비해 가독성과 사용성 측면에서 좋았고, GitHub의 웹 인터페이스에서 Actions 탭을 통해 실시간으로 결과를 확인할 수 있다는 점도 큰 장점인 것 같다. Self-hosted Runner를 활용하면 다양한 환경에서 유연하게 활용할 수 있을 것 같다.
또한, 스터디를 진행하면서 github actions를 쿠버네티스 환경에서 보다 효율적으로 관리할 수 있는 ARC(Actions Runner Controller)라는 컨트롤러의 존재에 대해 알게 되었는데, 다음 포스팅에서는 ARC를 활용한 쿠버네티스 내 Self-hosted Runner 관리와 GitOps 방식의 CI/CD 구현에 대해 조금 더 구체적으로 알아보자.
'CICD' 카테고리의 다른 글
jib를 활용하여 젠킨스 파이프라인 구성하기(GitOps + SpringBoot + Gradle ) (2) | 2024.12.08 |
---|---|
docker build 시, 빌드 명령어 결과 출력하기(--progress=plain) (0) | 2024.03.18 |
ArgoCD Image Updater를 활용한 Harbor RegistryOps 구축 가이드 (0) | 2023.12.12 |
ArgoCD 외부 EKS 클러스터 연동(Add Cluster) (0) | 2023.12.02 |
사설 이미지 저장소(Harbor)에 컨테이너 이미지 푸시 (0) | 2023.09.02 |