| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- SEQUENCE
- 클린아키텍처
- JPA
- index
- TDD
- string
- springsecurity
- 디스크i/o
- OOP
- 로그백
- Ajax
- 설정세팅
- 이벤트핸들러this
- MVC요청플로우
- 커뮤니티서버
- JDBC
- 냄새라도
- MVC
- 사면초가
- 단위테스트
- SpringLegacy
- AOP
- 공통기술
- Transaction
- 공통규약
- 문제선택근거
- 전전긍긍
- 이벤트핸들러등록
- model
- DDD
- Today
- Total
개발이군고구마
[멱살잡고 DevOps] 6. GitHub Actions CI/CD 구축 본문
[Backend Dockerfile]
1. backend Dockerfile 작성 🎆
[AWS Front 설정]
2. S3 버킷 & CloudFront 정적 웹 호스팅 활성화
[AWS EC2 설정]
3. EC2 인스턴스 프로비저닝
4. EC2 환경 구성 🎆
[CI/CD] <- 현 단계
5. GitHub Actions CI/CD 구축 🎇
[테스트와 확장]
6.DNS 설정 및 최초 배포 테스트
7. 배포환경 분리 (dev/prod)
코드를 main 브랜치에 push 하기만 하면, 자동으로 테스트, 빌드, 배포가 완료
🤔 왜 CI/CD가 필요한가? (CI/CD의 철학)
CI (Continuous Integration, 지속적 통합): 여러 개발자가 작성한 코드를 주기적으로 통합하고, 자동으로 테스트하여 코드의 충돌이나 버그를 조기에 발견하는 개발 프랙티스
코드를 push할 때마다 자동으로 테스트와 린트(코드 스타일 검사)를 실행
CD (Continuous Deployment, 지속적 배포): CI 단계를 통과한 코드를 자동으로 실제 운영 환경까지 배포하는 것을 의미
이를 통해 개발자는 코드 작성에만 집중할 수 있고, 배포 과정의 반복적인 수작업과 인적 실수를 완전히 제거
1. GitHub Secrets 설정: 민감 정보 안전하게 보관하기
1) 백엔드 / 프론트 IAM 분리
ㄴ 현관문 열쇠(프론트엔드 권한)와 자동차 키(백엔드 권한)는 따로 만드는 원리
- 프론트엔드 파이프라인의 역할: 코드를 빌드해서 S3에 올리고(s3:Sync), CloudFront 캐시를 무효화(cloudfront:CreateInvalidation)하는 것
- 백엔드 파이프라인의 역할: EC2 인스턴스에 접속해서 배포 스크립트를 실행(ssm:SendCommand)하는 것
✅ 역할 + 정책 과 사용자 + 정책의 차이 원리
- 사람은 사용자(User)
- 사람이 직접 로그인해서 작업
- 서비스나 인프라는 역할(Role) 을 통해 리소스에 접근하도록 분리해서 관리
- 사람은 아니지만 역할(Role)을 통해 권한을 임시로 획득
루트
- EC2에 역할 부여 -> 정책 연결


프론트 IAM 사용자
- 정책


백엔드 IAM 사용자
- 정책


2) Secrets 등록

2. 백엔드 CI/CD 워크플로우 작성
1) .dockerignore로 최종 배포 패키지 최적화
💢 Docker 에 올릴 이미지, 소스코드 전체를 Docker 데몬으로 보내게 됨 (!=빌드와 별개)
- __test__ 폴더, node_modules, .git 폴더 등 실제 서비스 실행에 필요 없는 파일까지 전부 포함됨
- 이미지 용량 증가, 빌드 속도 저하, 보안 위험 등의 문제가 발생함
- 빌드(주방) 는 __test__ 폴더 포함하며, docker hub 에 올릴 이미지(포장) 는 dockerignore를 제외
2) GitHub Actions 조건부 실행
✅ 1단계 PR 라벨 _ PR단계에서 코드의 품질을 자동으로 검증해줌 (마치, STG에 MR를 작성할 때 빌드 확인해주는 원리)
🔮 라벨 방식
- 아직 머지되지 않은 Pull Request에 특정 행동을 촉발시킬 때 매우 유용
- 코드 스타일: 정해진 코딩 규칙(Lint)을 잘 지켰는가?
- 기본 테스트: 기존의 기능들이 고장 나지 않았는가? (Unit/Integration Test)
- 빌드 가능 여부: 이 코드가 실제로 실행 가능한 상태인가? (Build Check)
기본적으로 unit + label이 있다면 integration
- 선택적 테스트 실행: PR이 "integration-tests" 라는 라벨을 붙이면 run-db-integration-tests 라벨을 붙여 무거운 통합 테스트를 수동으로 실행
- 자동화된 분류: PR의 내용에 따라 봇이 자동으로 bug나 enhancement 같은 라벨을 붙여주는 등의 작업을 할 때 사용
pull-request-check.yml
✅ 2단계 main push 태그 _ 두 개의 잡(Job)으로 분리하고, 조건부 실행 (if) 과 잡 의존성 (needs) 을 활용 (PRD_20250725)
🎴 태그 방식
- 특정 코드 버전(커밋)을 공식적으로 지정하고 중요한 작업을 할 때
- 예) 운영/스테이징 서버 배포 - v1.0.0, v1.1-stg 명확한 버전을 지정하여 실제 서버에 배포할 때 사용
- "최종 배포" 에는 태그 방식이 압도적
- GitHub 웹사이트에서 직접 태그 생성 또는 개발자가 push 할 때 tag 생성 후 push tag
# git tag -a [태그이름] -m "[태그에 대한 설명]"
git tag -a test-v1.0 -m "회원가입 기능 테스트 및 배포"
# git push [원격저장소이름] [태그이름]
git push origin test-v1.0
backend-ci-cd.yml
frontend-ci-cd.yml



3. 발생 오류
1) 백엔드
✅ GitHub Actions 동작 원리
- uses: owner/repo@ref
- GitHub API로 https://api.github.com/repos/owner/repo/git/refs/tags/ref 등을 조회
- 다운로드 후 워크플로우 런너에서 압축 해제
- 리포지토리나 ref(tag/branch/sha)가 없으면
→ “action not found” 오류 발생
🌐 cicd 스크립트 github action 메뉴얼로 수정
- name: AWS SSM Command
uses: Castlenine/aws-ssm-command@v1
id: ssm
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # You can inject any environment variable in the command execution. Don't use "-" for the environment name. Use "_" instead. Make sure that your environment variables are not creating a conflict. Any environment variable with "SSM_IGNORE" in the name will not be exported in the command execution
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
instance-ids: ${{ secrets.INSTANCE_ID }}
working-directory: '/home/ubuntu'
script-parent-folder-path: '.github' # Can be the main parent folder or with subfolders
command: '.github/scripts/example.sh' # Must be in the parent folder path
comment: 'Bash script executed by Github Actions'
ㄴ AWS-RunShellScript는 “AWS 퍼블릭(관리) 문서”
✅ GitHub Actions ↔ AWS 호출 원리
- configure-aws-credentials@v2
- IAM 키를 받아 워크플로우 러너 환경 변수로 설정
- 이후 모든 AWS API 호출(aws cli 또는 SSM 액션)이 이 크레덴셜을 사용해 요청을 서명(Signature V4)
- Identity-based Policy
- “내 액세스 키(Principal)” → “ssm:SendCommand(Action)” → “문서·인스턴스(Resource)”
- 명시적 Allow 없으면 디폴트 거부(Deny)
- 리소스 ARN 매칭
- 정책의 ARN과 실제 호출 대상 ARN이 정확히 일치해야 매칭
- 퍼블릭(관리) 리소스는 계정 ID 와일드카드(*)를 활용
Error: Failed to send or execute SSM command: User: arn:aws:iam::***:user/backend-deploy-user is not authorized to perform: ssm:GetCommandInvocation on resource: arn:aws:ssm:***:***:* because no identity-based policy allows the ssm:GetCommandInvocation action
ㄴ IAM 정책의 Resource 범위를 잘못 지정
✅ IAM 정책의 'Resource' 범위
- SSM SendCommand는 '비동기(Asynchronous)'로 동작
- AWS IAM은 “API 호출(Action)” 단위로 권한을 체크
- 명령 전송 (ssm:SendCommand): GitHub Actions가 EC2에 "이 명령어들을 실행해줘!"라고 요청. 이것은 '택배를 발송하는 행위'
- 결과 확인 (ssm:GetCommandInvocation): 요청을 보낸 후, GitHub Actions는 EC2에서 그 명령이 잘 실행되었는지, 성공했는지, 실패했는지 결과를 계속 확인(Polling). 이것은 '내 택배가 잘 도착했는지 배송 조회를 하는 행위'
🌐 IAM 정책 수정
기존
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": [
"arn:aws:ssm:ap-northeast-2:{내계정}:document/AWS-RunShellScript",
"arn:aws:ec2:ap-northeast-2:{내계정}:instance/{인스턴스아이디}
]
}
]
}
- AWS-RunShellScript는 “AWS 퍼블릭(관리) 문서” ARN과 매치되지 않아 Policy에서 거부
{
"Effect": "Allow",
"Action": [
"ssm:ListCommands",
"ssm:ListCommandInvocations",
"ssm:GetCommandInvocation"
],
"Resource": [
"arn:aws:ssm:ap-northeast-2:{내계정}:command/*/*" // ◀️ 이 부분이 문제
]
}
- GetCommandInvocation 액션은 특정 command-id에 대해 실행되는데, command/*/* 라는 ARN 패턴은 유효하지 않아 매칭되지 않음
- 액션의 실행 결과를 확인하는 권한은, 그 결과물(자원)이 생성되기 전이라 정확한 ARN을 예측하기 어려워 범위를 좁게 설정하기가 까다로움
수정후
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSendCommandToSpecificInstance",
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": [
"arn:aws:ssm:ap-northeast-2:*:document/AWS-RunShellScript",
"arn:aws:ec2:ap-northeast-2:{내계정}:instance/{인스턴스ID}"
]
},
{
"Sid": "AllowGetCommandInvocation",
"Effect": "Allow",
"Action": "ssm:GetCommandInvocation",
"Resource": "*"
}
]
}
- 기존의 **명령을 보내는 권한(SendCommand)**은 보안을 위해 특정 인스턴스로 범위를 좁혀두고, **결과를 확인하는 권한(GetCommandInvocation)**은 모든 리소스("*")에 대해 허용
2) 프론트엔드
ㄴ frontend-deploy-user의 IAM 정책에는 파일을 올리고 지우는 s3:PutObject, s3:DeleteObject 권한은 있지만,
ㄴS3 버킷의 파일 목록을 조회하는 s3:ListBucket 권한이 없어서 1번 단계에서 실패
ㄴ ! 버킷 이름 오타
✅ aws s3 sync의 동작 원리
5. 빌드 결과물을 S3에 업로드 (sync)
- name: Deploy to S3
run: |
aws s3 sync ./build s3://${{ secrets.AWS_S3_BUCKET_NAME }} --delete
aws s3 sync 명령어는 단순히 파일을 올리기만 하는 cp (copy) 명령어와 다르게 동작
- 대상 확인: 먼저 S3 버킷에 어떤 파일들이 있는지 목록을 확인. (이 단계에서 s3:ListBucket 권한이 필요)
- 비교: 로컬의 build 폴더 내용과 S3 버킷의 파일 목록을 비교.
- 작업 수행:
- S3에 없는 새 파일은 업로드 (s3:PutObject)
- S3에 있지만 로컬에 없는 파일은 삭제 (s3:DeleteObject, --delete 옵션 때문에)
- 내용이 변경된 파일은 덮어쓰기 (s3:PutObject)
🌐 IAM 정책 수정
기존
{
"Sid": "AllowS3ManageStaticSite",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::{버킷이름}",
"arn:aws:s3:::{버킷이름}/*"
]
},
IAM 정책에서 버킷 자체에 대한 권한과 버킷 안의 객체(파일)들에 대한 권한은 별개로 취급.
- arn:aws:s3:::-bucket : 버킷 자체 (ListBucket 권한 필요)
- arn:aws:s3:::-bucket/* : 버킷 안의 모든 객체 (PutObject, DeleteObject 등 권한 필요)
- “버킷”과 “객체” 두 리소스에 모두 ListBucket을 적용해 버렸기 때문에 AWS 내부 검증에서 거부될 수 있음
수정 후
{
"Sid": "ListBucketForSync",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3::: {버킷이름}"
},
{
"Sid": "ManageBucketObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::{버킷이름}/*"
]
},'Cloud Deploy' 카테고리의 다른 글
| [멱살잡고 DevOps] 8. 확장성 고민과 ESC 적용 (0) | 2025.07.29 |
|---|---|
| [멱살잡고 DevOps] 7. DNS 설정 및 테스트 (트러블 슈팅🎯) (0) | 2025.07.26 |
| [멱살잡고 DevOps] 5. AWS EC2 설정 (0) | 2025.07.21 |
| [멱살잡고 DevOps] 4. AWS S3/Cloudfront 설정 (1) | 2025.07.18 |
| [멱살잡고 DevOps] 3. Backend Dockerfile (docker compose/docker hub) (0) | 2025.07.17 |