김선영
한세대학교 22학번 컴퓨터공학과
한세대학교 22학번 컴퓨터공학과
CI/CD (Continuous Integration/Continuous Deployment)는 코드 변경사항을 자동으로 테스트하고 배포하는 개발 방식입니다.
이 글에서는 GitHub Actions를 사용한 반자동화 워크플로우를 구축하면서 겪은 실제 문제들과 해결 방법을 초보자 관점에서 정리했습니다.
반자동화(Semi-Automation): 사람의 설정과 트리거는 필요하지만, 이후 과정은 자동으로 실행되는 방식
GitHub Pages 블로그에 최근 커밋 활동을 자동으로 표시하기
GitHub Actions에서 리포지토리에 자동으로 커밋하려면 인증이 필요합니다.
1단계: SSH 키 쌍 생성
# PowerShell 또는 터미널에서 실행
ssh-keygen -t rsa -b 4096 -C "your_deploy_key" -f deploy_key
이 명령어는 두 개의 파일을 생성합니다:
deploy_key (Private Key - 비밀!)deploy_key.pub (Public Key - 공개 가능)2단계: Public Key를 GitHub에 등록
# Public Key 내용 보기
cat deploy_key.pub
GitHub 리포지토리 → Settings → Deploy keys → Add deploy key
Actions Deploy Key3단계: Private Key를 GitHub Secret에 등록
# Private Key 내용 보기
cat deploy_key
GitHub 리포지토리 → Settings → Secrets and variables → Actions → New repository secret
ACTIONS_DEPLOY_KEY# YAML 파일에서
ssh-private-key: $
# 실제 등록된 이름
ACTIONS_DEPLOY_KEY
증상:
Error: The ssh-private-key argument is empty.
해결: Secret 이름을 정확히 일치시키기
ssh-private-key: $
증상: 키를 등록했는데도 같은 에러 발생
원인:
# 생성된 키 형식 (OpenSSH)
-----BEGIN OPENSSH PRIVATE KEY-----
# GitHub Actions가 원하는 형식 (RSA)
-----BEGIN RSA PRIVATE KEY-----
해결: 키 형식 변환
ssh-keygen -p -f deploy_key -m pem
# Enter old passphrase: (엔터)
# Enter new passphrase: (엔터)
# Enter same passphrase again: (엔터)
증상:
ERROR: The key you are authenticating with has been marked as read only.
fatal: Could not read from remote repository.
원인: Deploy Key 생성 시 “Allow write access” 체크 안 함
해결:
SSH Deploy Key 대신 GitHub이 자동으로 제공하는 토큰 사용:
- name: Checkout code
uses: actions/checkout@v4
with:
token: $ # 자동 제공, 설정 불필요!
장점: 별도 설정 없이 바로 작동 단점: 해당 리포지토리에만 접근 가능
GitHub API에서 받은 JSON 데이터를 Bash 스크립트로 처리해야 했습니다.
# ❌ 이렇게 하면 안 됩니다!
COMMITS=$(curl -s https://api.github.com/.../commits | jq '.[]')
for COMMIT in $COMMITS; do
ALL_COMMITS=$(echo "$ALL_COMMITS" | jq --argjson commit "$COMMIT" '. + [$commit]')
done
증상:
jq: invalid JSON text passed to --argjson
Error: Process completed with exit code 2.
왜 안 되나요?
Bash의 for 루프는 공백과 줄바꿈을 기준으로 문자열을 나눕니다. JSON은 공백이 많아서 중간에 잘립니다:
// 원하는 것
{"repo": "test", "message": "fix"}
// 실제로 들어가는 것 (조각남!)
{"repo":
"test",
"message":
# 1. JSON 객체들을 가져오기
COMMITS=$(curl -s https://api.github.com/.../commits | jq -c '.[] | {...}')
# 2. 여러 줄의 JSON을 하나의 배열로 변환 (slurp)
NEW_COMMITS_ARRAY=$(echo "$COMMITS" | jq -s '.')
# 3. 안전하게 병합
ALL_COMMITS=$(echo "$ALL_COMMITS" | jq --argjson new_commits "$NEW_COMMITS_ARRAY" '. + $new_commits')
핵심: jq -s는 여러 JSON 객체를 읽어서 배열 [...]로 만들어줍니다!
curl https://api.github.com/users/sunbang123/repos?type=owner
# 결과: {"message": "Not Found", "status": "404"}
브라우저에서는 잘 되는데 Actions에서만 404!
?type=owner 파라미터가 문제였습니다.
GitHub Actions의 GITHUB_TOKEN은 제한된 권한을 가져서, 이 필터가 제대로 작동하지 않았습니다.
# ❌ 이렇게 하면 404
REPOS_URL="https://api.github.com/users/sunbang123/repos?type=owner"
# ✅ 파라미터 제거
REPOS_URL="https://api.github.com/users/sunbang123/repos"
GitHub Actions가 파일을 생성하고 푸시까지 성공했는데, 웹사이트에서는 파일을 못 찾음:
Error: 커밋 데이터 파일(commits_data.json)을 찾을 수 없습니다.
Jekyll은 _로 시작하는 폴더를 기본적으로 무시합니다!
_data/commits_data.json ← Jekyll이 빌드 시 제외!
_config.yml 파일에 추가:
include:
- _data
이제 Jekyll이 _data 폴더를 빌드에 포함합니다.
# .github/workflows/fetch_commits.yml
name: Fetch GitHub Commits
on:
push:
branches:
- main
workflow_dispatch: # 수동 실행 버튼
schedule:
- cron: '0 0 * * *' # 매일 자정 자동 실행
permissions:
contents: write # 파일 생성/커밋 권한
jobs:
fetch_and_save:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: $
- name: Install jq
run: sudo apt-get install -y jq
- name: Fetch commits
env:
GITHUB_USERNAME: sunbang123
GITHUB_TOKEN: $
run: |
REPOS_URL="https://api.github.com/users/${GITHUB_USERNAME}/repos"
REPO_NAMES_RAW=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" $REPOS_URL)
# 에러 체크
if ! echo "$REPO_NAMES_RAW" | jq -e '.[].name' > /dev/null 2>&1; then
echo "Failed to fetch repos"
exit 1
fi
REPO_NAMES=$(echo "$REPO_NAMES_RAW" | jq -r '.[].name')
ALL_COMMITS="[]"
for REPO_NAME in $REPO_NAMES; do
COMMITS_URL="https://api.github.com/repos/${GITHUB_USERNAME}/${REPO_NAME}/commits?per_page=5"
COMMITS_RAW=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" $COMMITS_URL)
if echo "$COMMITS_RAW" | jq -e '.[0].sha' > /dev/null 2>&1; then
COMMITS=$(echo "$COMMITS_RAW" | jq -c --arg repo "$REPO_NAME" '
.[] | {
repo: $repo,
message: .commit.message,
author: .commit.author.name,
date: .commit.author.date,
sha: .sha
}
')
if [ -n "$COMMITS" ]; then
# 핵심: jq -s로 안전하게 배열 변환
NEW_COMMITS_ARRAY=$(echo "$COMMITS" | jq -s '.')
ALL_COMMITS=$(echo "$ALL_COMMITS" | jq --argjson new "$NEW_COMMITS_ARRAY" '. + $new')
fi
fi
done
FINAL=$(echo "$ALL_COMMITS" | jq 'sort_by(.date) | reverse')
mkdir -p _data
echo "$FINAL" > _data/commits_data.json
- name: Commit and push
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update commits data"
file_pattern: _data/commits_data.json
ssh-keygen -p -m pem)for 루프로 JSON 객체 직접 순회jq -s로 배열 변환 후 --argjson으로 병합?type=owner 같은 필터 파라미터는 조심jq -e로 유효성 검사)_로 시작하는 폴더는 _config.yml에 명시적으로 포함이 가이드를 따라 했다면:
다음으로 시도해볼 것:
Tags: GitHub Actions, CI/CD, 초보자, 자동화, Bash, jq, Jekyll