[CI/CD]2주차 - GitHub Actions CI/CD
이번 포스팅은 Github Actions를 통한 자동화 구성이다
서버 안에서 코드 수정도 해 보고 Github Actions 몇몇 기능도 사용해 볼 것이다.
1. 직접 개발 후 실행
자동화의 필요성을 알기 위해 먼저 서버에서 직접 github에 코드 반영을 진행해 볼 것이다
Python 3.10.12이 설치된 서버 하나를 띄우고 아래 코드를 실행한다.
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
코드 실행 후 다른 터미널에서 접속해 보았다
이제 서버 세팅은 끝났고 git 세팅해야 한다
먼저 Settings > Developer Settings > Personal access tokens > Tokens 에서 토큰을 발급하자
권한은 repo, workflow만 주면 된다
private repo으로 파이썬 .gitignore 파일 생성되게 하나 만들었다
서버로 돌아와서 직접 깃을 사용해 보자!
우선 매번 자격증명을 물어볼 때마다 입력하기 번거로우니 global 옵션으로 저장한다
git config --global user.name [username]
git config --global user.email [email]
git config --global credential.helper store
git clone https://github.com/$GITUSER/cicd-2w.git
으로 소스코드를 땡겨오면 서버에 레포를 가져올 수 있다
위에서 만든 server.py를 해당 디렉토리로 옮긴 후 레포에 push해 주면 실제 레포에도 올라간 것을 확인할 수 있다.
여기서 .gitignore
파일은 git 저장소에 안올라가게 무시할 파일을 명시할 수 있는데 그 중 로그파일이 있다.
정말 실제로 안올라 가는지 테스트해 보자!
일단 서버 로그를 찍어서 파일을 생성하고
깃에 올려보면 낫띵투커밋 어쩌고가 뜬다. 올릴게 없다는 뜻!
위에 띄워둔 서버를 sudo fuser -k -n tcp 80
로 죽이고 (80포트 열려있는 프로세스는 다 kill한다는 명령어이다)
소스코드를 바꾸고 다시 git 저장소에 push를 해 본다
흠… “소스코드 변경 → commit & push → 대상서버에 반영” 이 과정이 생각보다 번거롭다
지금은 대상서버에서 바로 작업했기 때문에 “소스코드 변경 → commit & push” 이 부분만 진행한거지만 모든 개발자가 다 이렇게 작업할 수도 없고 일반적으로 로컬PC에 IDE 툴을 사용하기 때문에 훨씬 더 번거로워 진다.
그래서 이 때! Github Actions를 사용하여 일련의 과정을 자동화해 볼 것이다!
2. GitHub Actions -1-
먼저 세팅해 줄 것이 있다.
Github Actions의 서버가 내 대상서버 EC2에 접근하기 위해 EC2의 Public IP와 SSH 프라이빗키를 Secret 키로 등록해 줘야 한다.
레포의 Settings > Secrets and variables > Actions 에서 등록하면 된다.
타인에게 노출되면 안되는 값은 Secrets 값으로 등록해 주고 민감하지 않은 정보는 variables에 등록할 수 있다. Secrets은 데이터가 암호화되어 저장되기 때문에 안전하게 사용할 수 있다.
이제 로컬PC로 돌아와 작업을 진행한다. git레포를 먼저 clone해 오고
.github/workflows/deploy.yaml
파일을 생성한다. Github Actions이 동작할 때 이 경로에 있는 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 "$" > ~/.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="$"
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="$"
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 &"
-
on: workflow_dispatch
: 워크플로우가 트리거되는 이벤트를 정의하는건데 수동으로 push할 때 트리거된다. 워크플로우에서 Run workflow 버튼을 클릭해서 수동 배포 가능하다. on: push:
조건은 main 브랜치에 push할 때 자동실행된다- job은 워크플로우에서 수행할 작업을 의미하는데 steps로 정의해서 파이프라인을 구성할 수 있다.
-
Github Actions의 가상컴퓨터환경이 대상 서버에 접근하기 위해 ssh 설정을 해 주고
StrictHostKeyChecking=no
옵션을 통해 ssh 접근할 때 뜨는Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
이 부분을 생략할 수 있다.그 다음 서버에 접근해서 git pull을 진행해 서버에 변경된 코드를 반영해 주고 기존 프로세스를 죽인 후에 서버를 재실행하게 해 준다
push하고 레포에 들어가 보면 Actions 탭에 새로운 workflow가 생겨 있다
그럼 deploy.yaml에 정의한 대로 워크플로우가 실행된다.
Ubuntu 환경의 서버가 job을 실행하는 과정이다.
ssh 키를 설정하는 데에 사전에 설정한 Secrets 값을 가져와서 진행한다. 이 때 암호화되어 노출되지 않는 부분을 확인할 수 있다.
대상 서버에서 git pull 과정을 거치고 코드를 실행하고 나서 job을 Complete한다
직접 서버에 들어가서 확인해 보면 코드 반영 및 서버 실행이 정상 동작중인 것을 알 수 있다.
소스코드를 변경하고 deploy.yaml에 workflow 이름과 job 이름만 수정해서 push해 보았다.
name: CICD1 End
on:
workflow_dispatch:
push:
branches:
- main
jobs:
deployfinal:
runs-on: ubuntu-latest
steps:
.....
.....
github에 들어가서 확인해 보면 workflow와 job 이름이 변경된 걸 확인할 수 있었다
하지만 매번 스크립트를 짜기에는 좀 번거로움이 있다.
그래서 Github Actions 기능을 좀 더 활용해 보고자 한다!
아래 실습에서 Secrets과 Extentions을 활용해 볼 것이다.
3. GitHub Actions -2-
우선 Github Actions에 job이 실행되는 환경에서 사용하는 파이썬 버전 정보나 env를 확인해 보자
name: CICD2
on:
workflow_dispatch:
push:
branches:
- main
jobs:
deployfinal:
runs-on: ubuntu-latest
steps:
- name: Test
run: |
python -V || true
python3 -V || true
which python || true
which python3 || true
env
파이썬 3.10.12를 사용하고 여러 환경변수를 확인할 수 있었다
GitHub Actions에서 .gitignore
에 제외된 민감파일 내용을 안전하게 가져오기
.env
파일이 포함되어 있다
역시나 .env
파일 푸시는 무시된다.
이 때 Github Actions에서 Secrets을 생성해서 사용하면 안전하게 가져올 수 있다
이제 Extentions을 활용해 볼건데,
Github에는 Marketplace에서 사용자가 스크립트를 간편하게 사용할 수 있게 커스텀해서 만들어진 extentions을 사용할 수 있다
deploy.yml을 수정해서 appleboy/ssh-action@v1.2.0
를 사용해 볼 것이다!
name: CICD2
on:
workflow_dispatch:
push:
branches:
- main
jobs:
ssh-deploy:
runs-on: ubuntu-latest
steps:
- name: Github Repository Checkout
uses: actions/checkout@v4
- name: executing remote ssh commands
uses: appleboy/ssh-action@v1.2.0
env:
AWS_KEYS: $
with:
host: $
username: ubuntu
key: $
envs: AWS_KEYS
script_stop: true
script: |
cd /home/ubuntu/cicd-2w
echo "$AWS_KEYS" > .env
actions/checkout@v4
: GitHub의 기본 액션이며 현재 워크플로우가 실행되는 레포의 코드를 체크아웃할 수 있다.appleboy/ssh-action@v1.2.0
: Github Actions에 저장된 Secrets 값을 변수로 가져와서 ssh 접속 후에 스크립트를 실행할 수 있다echo "$AWS_KEYS" > .env
: Secrets 값을 통해 민감한 정보를 서버에 안전하게 전달할 수 있다
번거롭긴 하지만 .env
파일을 업데이트하려면 Github Actions Secrets 값을 업데이트해 주어야 한다!
SCP 사용하여 서버에 코드 배포하기
스크립트를 통해 매번 git pull을 하지 않고 SCP 파일 전송 프로토콜로 서버에 직접 배포가 가능하다
이전에는 “로컬PC에서 코드변경 & push → Github Actions 트리거 → 대상서버 ssh 접속 → git pull → 서비스중지 후 실행” 방식으로 이루어 졌다면
지금은 ssh, scp 익스텐션을 사용해서 “로컬PC에서 코드변경 & push → Github Actions 트리거 → git checkout → 대상서버 ssh 접속 → 서비스 중지 → 파일 복사” 방식으로 바꿀 수 있다 (서비스 실행까지는 바로 다음 실습에 이어서!)
소스코드를 살짝 변경하고 deploy.yml도 아래처럼 수정했다
name: CICD2
on:
workflow_dispatch:
push:
branches:
- main
jobs:
scp-ssh-deploy:
runs-on: ubuntu-latest
steps:
- name: Github Repository Checkout
uses: actions/checkout@v4
- name: executing remote ssh commands
uses: appleboy/ssh-action@v1.2.0
env:
AWS_KEYS: $
with:
host: $
username: ubuntu
key: $
envs: AWS_KEYS
script_stop: true
script: |
cd /home/ubuntu/cicd-2w
echo "$AWS_KEYS" > .env
sudo fuser -k -n tcp 80 || true
- name: copy file via ssh
uses: appleboy/scp-action@v0.1.7
with:
host: $
username: ubuntu
key: $
source: server.py
target: /home/ubuntu/cicd-2w
appleboy/scp-action@v0.1.7
: ssh 접속하고 나서 source에 지정된 파일을 target 경로에 복사해 줄 수 있다
이제 최종적으로 변경된 코드로 서비스 재기동까지 해 보겠다
“로컬PC에서 코드변경 & push → Github Actions 트리거 → git checkout → 대상서버 ssh 접속 후 파일 복사 → ssh 접속 후 서비스 중지 → 복사한 파일 이동 → 서비스재기동” 순서로 이루어 진다.
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: $
username: ubuntu
key: $
source: server.py
target: /home/ubuntu
- name: executing remote ssh commands
uses: appleboy/ssh-action@v1.2.0
env:
AWS_KEYS: $
with:
host: $
username: ubuntu
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
아무것도 설치되어 있지 않은 신규 서버에 배포한다고 가정해 보고 다른 서버 생성해서 배포해 보겠다
우선 Github Actions Secrets 값에 신규 서버 ip로 변경하고 시작한다!
처음 배포한 서버에는 cicd-2w 디렉토리가 생성되어 있지 않으니 mkdir
로 생성해 줘야 하고 기존에 디렉토리가 있을 경우 에러가 나지 않게 || true
옵션을 줘야 한다. rm server.py
도 마찬가지다
script: |
mkdir -p /home/ubuntu/cicd-2w || true
cd /home/ubuntu/cicd-2w
echo "$AWS_KEYS" > .env
sudo fuser -k -n tcp 80 || true
rm server.py || true
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
근데 마지막 echo "test" >> /home/ubuntu/text.txt
을 실행할 때마다, 즉 배포할 때마다 test가 배포 수만큼 찍히게 된다
이런 문제점(?)으로 인해 선언형 배포툴로 ansible을 사용할 수 있다
4. GitHub Actions with Ansible
Github Actions에 ansible을 설치해서 진행한다
그럼 로컬에서 ansible을 사용하듯 Github Actions의 가상서버가 주체가 되어 ansible을 대상서버에게 사용할 수 있다!
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 "$" > ~/.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 "$" >> $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
파이썬 버전은 그대로 사용해도 되지만 3.8으로 버전을 명시했다
간단하게 ansible 설정을 해 두고
인벤토리에 정의된 모든 호스트를 대상으로 ping 요청을 보내 대상 서버와 연결이 가능한지를 확인할 수 있다.
ansible.cfg
파일에 inventory를 지정했기 때문에 ansible이 동작할 때 -i
옵션으로 인벤토리를 지정하지 않아도 된다
이제 test.txt 파일이 선언형으로 지정되는지 확인해 보고자 아래 내용만 추가해 주었다.
- name: Run Ansible Playbook
working-directory: ./devops/ansible/
run: |
echo '---
- name: Ensure text.txt contains only "test"
hosts: all
tasks:
- name: Ensure the file contains only "test"
lineinfile:
path: /home/ubuntu/text.txt
line: "test"
state: present
create: yes' > playbook.yml
ansible-playbook playbook.yml
기본적으로 ansible은 localhost에서 실행되기 때문에 기본적으로 local 인벤토리를 참조하게 되어서 이 때, working-directory: ./devops/ansible/
를 추가해 주어야 이 경로에서 인벤토리 파일을 불러올 수 있다
하나는 실행 전이고 하나는 실행 후이다. 동일하게 test가 찍힌다.
line: "test"
부분에 test가 아닌 test123으로 변경했을 때는 덮어쓰기가 아니라 라인 추가가 되었다.