Cloud Tech

Github Action CI/CD

Hanhorang31 2024. 12. 15. 01:44
 

Overview

Github Action 학습한 내용을 정리합니다.

참고는 Github Action 공식 문서와 CloudNet@ CI/CD 스터디를 참고하였습니다.

 

Github Action

Github Action 은 원격 레파지토리인 github가 제공하는 워크플로우 솔루션입니다.

사용자가 정의한 워크플로(Workflow)를 통해 CI/CD 등 애플리케이션 자동화 작업을 실행할 수 있습니다.

GitHub Actions는 서버리스(Serverless) 방식으로 동작하여, GitHub의 실행 환경(호스트된 실행 서버)이 사용자가 정의한 작업을 대신 실행합니다.

워크플로우 구성만 하면 별도의 실행 환경 구성없이 자동화할 수 있다는 점과 GIt 이벤트(PR, 푸쉬, Issue) 에 유유연합니다. 젠킨스가 다양한 확장 플러그인을 제공하여 더욱 유연하지만, 실행 서버 관리가 필요없고 깃허브에서 코드를 관리한다면 좋은 선택지가 될 것 같습니다.

 

Github Action 구성요소

  • Event(이벤트): 워크플로를 시작하는 트리거 동작이나 조건입니다.
  • Runner(실행기): Job을 실행하는 호스팅 또는 셀프 호스트 환경입니다. 리눅스, 윈도우 등의 실행 환경을 설정할 수 있습니다.
  • Job(작업): 여러 Step으로 구성된 단위 작업입니다. 작업 간 병렬 실행 또는 의존성 설정(순차 실행)이 가능합니다.
  • Step(단계): Job 내에서 실행되는 개별 작업 명령입니다. 스크립트 실행, Action 호출 등이 포함됩니다.

각 단계는 repo 내 .github/workflows 경로의 yaml 파일 형식으로 구성합니다.

name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 
on: [push] # Event
jobs: # Jobs 
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest # Runner 
    steps: # Step 
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/checkout@v4
      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."

 

실행 환경 구성 (빌드 - 배포)

아마존 EC2 서버를 생성하고, 깃허브 액션을 통해 서버의 애플리케이션 배포를 자동화하겠습니다.

실행 환경 : (AWS) Ubuntu 22.04 amd64 t3.small
# 서버 내 실행
cat > server.py <<EOF
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from datetime import datetime

class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        now = datetime.now()
        response_string = now.strftime("The time is %-I:%M:%S %p, CloudNeta Study.\n")
        self.wfile.write(bytes(response_string, "utf-8")) 

def startServer():
    try:
        server = ThreadingHTTPServer(('', 80), RequestHandler)
        print("Listening on " + ":".join(map(str, server.server_address)))
        server.serve_forever()
    except KeyboardInterrupt:
        server.shutdown()

if __name__== "__main__":
    startServer()
EOF


sudo python3 server.py

# 새터미널 서버 동작 확인
curl localhost
sudo ss -tnlp

깃허브 Repo를 생성하여 위 서버 코드를 올리겠습니다.

깃허브 작업은 다음과 같이 작업이 필요합니다.

  1. 깃허브 개인 계정 접근 토큰 발급(계정 > Setting > Developer Settings> Personal Access tokens 발급)
  2. Private Repo 생성
  3. 깃허브 내 EC2 서버 접근 키 입력
  4. 깃허브 액션 워크플로우 파일 작성
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

git clone https://github.com/HanHoRang31/cicd-2w.git
Cloning into 'cicd-2w'...
Username for 'https://github.com': 
Password for 'https://tmdgh663@gmail.com@github.com': <위 접근 토큰 입력>

cp server.py cicd-2w/
cd cicd-2w 

git config --global user.email "you@example.com"
git config --global user.name "Your Name"
  
git status
git add .
git commit -m "first commit"
git push origin main

 

깃허브 접근 키는 깃허브 > Repo > Setting > secrets and variables > secrets 에서 입력합니다.

  • EC2_PIP ( EC2 서버 Public IP)
  • SSH_PRIVATE_KEY ( 서버 접근 키)

이어서, .github/workflows/deploy.yaml 를 생성하여 다음과 같이 코드를 구성합니다.

name: CICD1
on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Configure the SSH Private Key Secret
        run: |
          mkdir -p ~/.ssh/
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa

      - name: Set Strict Host Key Checking
        run: echo "StrictHostKeyChecking=no" > ~/.ssh/config

      - name: Git Pull
        run: |
          export MY_HOST="${{ secrets.EC2_PIP }}"
          ssh ubuntu@$MY_HOST << EOF
            cd /home/ubuntu/cicd-2w || exit 1
            git pull origin main || exit 1
          EOF

      - name: Run service
        run: |
          export MY_HOST="${{ secrets.EC2_PIP }}"
          ssh ubuntu@$MY_HOST sudo fuser -k -n tcp 80 || true
          ssh ubuntu@$MY_HOST "nohup sudo -E python3 /home/ubuntu/cicd-2w/server.py > /home/ubuntu/cicd-2w/server.log 2>&1 &"
  • Event(이벤트): Main 브랜치 Push 시 잡을 실행합니다.
  • Runner(실행기): ubuntu-lastest에서 실행합니다.
  • Step(단계): Job 내에 시크릿 키 등록 작업, 깃허브 풀 작업, 서버 배포 작업이 순차적으로 동작합니다.

워크플로우 파일을 Repo에 업로드하면 자동으로 깃허브 액션이 활성화됩니다.

git add . && git commit -m "add workflow" && git push origin main

필자는 구성 서버에서 git 글로벌 정보를 입력하고 다시 푸쉬해주세요

git config --global user.name hanhorang
git config --global user.email hanhorang@gmail.com
git config --global credential.helper store

잘 동작합니다!

 

워크플로우 구성(빌드 - 테스트 - 배포)

위 구성한 배포 단계에 테스트 과정을 추가하고, 민감 정보를 Repo 공유 없이 Github Action에서 사용하도록 구성하겠습니다.

  • GitHub Actions에서 코드 가져오기
  • GitHub Actions에서 .gitignore 제외된 민감 파일 내용을 을 안전하게 가져와서 사용하기 ⇒ 매번 수동으로 가져오기 불편하다!
  • scp로 대상 서버 ec2 에 py 파일 전송
  • 대상 서버 ec2에 기존 서비스 중지하고 다시 실행

먼저 REPO 깃허브 액션에서 민감 정보를 새로 입력합니다.

워크플로우를 아래와 같이 수정합니다.

아래 코드에서 핵심은 uses 입니다. 깃허브 액션에서도 젠킨스 플러그인과 같이 사용자 편의성의 파이프라인을 구성 후 가져와서 사용할 수 있습니다.

예제에서는 SCP(appleboy/scp-action@v0.1.7) 와 민감정보 사용을 위한 SSH uses(appleboy/ssh-action@v1.2.0)를 사용하였습니다.

기타 플러그인은 깃허브 마켓플레이스에서 확인이 가능합니다.

 

name: CICD2
on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Github Repository Checkout
        uses: actions/checkout@v4

      - name: copy file via ssh
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.EC2_PIP }}
          username: ubuntu
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          source: server.py
          target: /home/ubuntu

      - name: executing remote ssh commands 
        uses: appleboy/ssh-action@v1.2.0
        env:
          AWS_KEYS: ${{ secrets.MYKEYS }}
        with:
          host: ${{ secrets.EC2_PIP }}
          username: ubuntu
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          envs: AWS_KEYS
          script_stop: true
          script: |
             cd /home/ubuntu/cicd-2w
             echo "$AWS_KEYS" > .env
             sudo fuser -k -n tcp 80 || true
             rm server.py
             cp /home/ubuntu/server.py ./
             nohup sudo -E python3 /home/ubuntu/cicd-2w/server.py > /home/ubuntu/cicd-2w/server.log 2>&1 &
             echo "test" >> /home/ubuntu/text.txt

git add . && git commit -m "Deploy CICD2 Final" && git push origin main

 

 

워크플로우 구성(With Ansible)

Github Action에 Ansible을 연동하면 절차적 형태가 아닌 선언적 형태로 CI/CD 파이프라인을 관리할 수 있습니다. 실제 운영 환경시 가독성과 재사용성을 높이고, 깃허브 액션에서 테스트와 배포를 Ansible을 통해 관리하여 쉽게 자동화할 수 있습니다.

Ansible 추가 워크플로우는 다음과 같이 구성할 수 있습니다.

 

Steps(단계):

  1. GitHub 리포지토리에서 코드를 가져옴
  2. Python 3.8 설치 및 Ansible 설치
  3. SSH 키를 설정하여 원격 서버와의 연결 준비
  4. Ansible 인벤토리 및 설정 파일 생성
  5. ping 테스트를 통해 원격 서버와의 연결 확인
  6. (주석 처리된) 플레이북 실행으로 서버 배포 작업 수행
name: Run Ansible
on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  run-playbooks:
    runs-on: ubuntu-latest
    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.EC2_PIP }}" >> $INVENTORY_FILE

      - name: Ansible Default Configuration File
        run: |
          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 = ubuntu
          inventory = ./inventory.ini
          host_key_checking = False
          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
Copy
git add . && git commit -m "Deploy Ansible Test" && git push origin main

 

참고

GIthub Action DoC

CloudNet@ CICD 스터디