์ฐ์ CI/CD ํ๊ฒฝ์ ๋ค์๊ณผ ๊ฐ๋ค.
docker: command not found ์๋ฌ๋ EC2 ์๋ฒ์ Docker๊ฐ ์ค์น๋์ด ์์ง ์์์ ๋ฐ์ํ ์ค๋ฅ๋ค. GitHub Actions์์ EC2๋ก SSH ์ ์์ ์ฑ๊ณตํ์ง๋ง, EC2 ์์ docker ๋ช
๋ น์ด ์์ฒด๊ฐ ์์ด์ ์ ๋ถ ์คํจํ ์ํฉ์ด๋ค.

EC2์ SSH ์ ์ ํ ์๋ ๋ช ๋ น์ด ์คํํด Docker๋ฅผ ์ค์นํ๋ค.
sudo apt update
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
์์ ๋น์ทํ๊ฒ aws: command not found ์๋ฌ๋ ๋ฐ์ํ๋ค. ์ด ์๋ฌ๋ EC2 ์๋ฒ์ awscli๊ฐ ์์ ์ค์น๋์ด ์์ง ์์์ ๋ฐ์ํ ์๋ฌ๋ก ์ญ์ awscli๋ฅผ ํ ๋ฒ ์ค์นํ๋ฉด ํด๊ฒฐ๋๋ค.

EC2์ SSH ์ ์ ํ ์๋ ๋ช
๋ น์ด ์คํํ๋ค. apt install awscli ๋ช
๋ น์ด๋ฅผ ํตํด ์ค์น๋ฅผ ํ๋ ค ํ์ง๋ง, Ubuntu 24.04๋ถํฐ๋ AWS์์ ์ ๊ณตํ๋ ๊ณต์ zip์ผ๋ก ์ค์นํด์ผ ํ๋ค๊ณ ํ๋ค.
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
๋ง์ฝ unzip๋ ์ค์น๋์ด์์ง ์๋ค๋ฉด ๋จผ์ ์ค์นํด์ค๋ค.
sudo apt update
sudo apt install -y unzip
์ค์น ํ, ๋ค์ ๋ช ๋ น์ด๋ก ์ ์์ ์ผ๋ก ์ค์น๋์๋์ง ํ์ธํ ์ ์๋ค.
aws --version
๐ค GitHub Actions์์๋ ์ aws ๋ช
๋ น์ด๊ฐ ๋๋๋ฐ?
GitHub Actions์์ aws ๋ช
๋ น์ด๊ฐ ๋์ํ๋ ์ด์ ๋, GitHub์ ๋ฆฌ๋
์ค ๋ฌ๋์ AWS CLI๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ค์น๋์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฐ๋ฉด EC2๋ ์ง์ ์ค์ ํ๋ ๊ฐ์ ์๋ฒ์ด๊ธฐ ๋๋ฌธ์, ํ์ํ ๋๊ตฌ๋ ๋ชจ๋ ์๋์ผ๋ก ์ค์นํด์ฃผ์ด์ผ ํ๋ค.
์ด์ aws ๋ช
๋ น์ด ์คํ์ ์ ๋์ง๋ง, Unable to locate credentials. You can configure credentials by running "aws configure". ์ด๋ผ๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ์ด ์๋ฌ๋ EC2์์ AWS ์ธ์ฆ์ ๋ณด(Access Key, Secret Key)๊ฐ ์์ ์๋ ์ํ์ฌ์ ๋ฐ์ํ ๊ฒ์ด๋ค.

์์ธํ ์์ธ์ ์ดํด๋ณด๋...
aws ecr get-login-password๋ฅผ ์ฐ๋ ค๋ฉด AWS IAM ๊ถํ์ด ์์ด์ผ ํ๋ค.~/.aws/credentials ํ์ผ์ด ์์ด์ ๋ฐ์ํ๋ค.๋ฐ๋ผ์ EC2์ IAM Role์ ์ฐ๊ฒฐํด ํด๊ฒฐํ๋ค. EC2 ์ธ์คํด์ค ์ธ๋ถ์ ๋ณด๋ฅผ ๋ณด๋, IAM Role์ด ๋น์ด์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.

๋ฐ๋ผ์ IAM ์ญํ ์ EC2๋ก ํด์ ์๋ก ๋ง๋ ํ, AmazonEC2ContainerRegistryFullAccess ๊ถํ์ ํ์ฉํ๋ค. ECR์ Private Registry๋ผ์ IAM ๊ถํ์ด ์์ผ๋ฉด pull์ ์คํจํ๋ค.

๋ค์๊ณผ ๊ฐ์ด ์ค์ ํด์ผ EC2๊ฐ ECR์์ pull/push ํฌํจ ํ ๊ถํ์ ๊ฐ์ง๋ค.

๋ฐฐํฌ ํ
์คํธ์์ ๋ค์๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.

์๋ฌ ๋ฉ์์ง๋ฅผ ๋ถ์ํด๋ณด๋ ๋ ๊ฐ์ง ๋ฌธ์ ๊ฐ ์์๋ค.
1. docker login ๊ด๋ จ ์๋ฌ
Your authorization token has expired.
์ด ๋ฉ์์ง๋ ECR ์ธ์ฆ ํ ํฐ์ด ๋ง๋ฃ๋์์์ ์๋ฏธํ๋ค. AWS ECR์ ๋ณด์์ ์ํด ๋ก๊ทธ์ธ ํ ํฐ์ด 12์๊ฐ ๋์๋ง ์ ํจํ๋๋ก ์ค๊ณ๋์ด ์๋ค.
๋๋ EC2 ์ธ์คํด์ค์ ์ด๋ฏธ AmazonEC2ContainerRegistryFullAccess IAM ์ญํ ์ ๋ถ์ฌํ๊ธฐ ๋๋ฌธ์, ๋ณ๋๋ก ๋ก๊ทธ์ธํ์ง ์์๋ ๋ ์ค ์์๋ค. ํ์ง๋ง ์ค์ ๋ก๋ ๊ทธ๋ ์ง ์์๋ค.
โ๏ธ ์ ๋ฆฌํ์๋ฉด...
EC2๊ฐ ECR์ ์ ๊ทผํ ๊ถํ(IAM Role)์ ์์ง๋ง, Docker๋ AWS IAM์ ์ธ์ํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ๋ก๊ทธ์ธ ์ ์ฐจ๊ฐ ํ์ํ๋ค.
๋ฐ๋ผ์ AWS CLI๋ฅผ ํตํด ์ธ์ฆ ํ ํฐ์ ๋ฐ์์ Docker์ ์ ๋ฌํด์ค์ผ ํ๋ค. ๋ค์ ๋ช
๋ น์ด๋ฅผ Deploy on EC2 ๋จ๊ณ์ ์ถ๊ฐํ์ docker pull์ด ์ ์์ ์ผ๋ก ์๋ํ๋ค.
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_URI
โ๏ธ IAM ๊ถํ๊ณผ Docker ์ธ์ฆ์ ๋ณ๊ฐ!
docker login์ผ๋ก ๋ช
์์ ์ผ๋ก ์ธ์ฆํด์ค์ผ ํ๋ค.๋ํ, ECR ์ธ์ฆ ํ ํฐ์ ๋จ๊ธฐ(12์๊ฐ ์ ํจ) ํ ํฐ์ด๊ธฐ ๋๋ฌธ์, ์ฃผ๊ธฐ์ ์ผ๋ก ์ฌ๋ก๊ทธ์ธํ๋ ๋ก์ง์ด ํ์ํ ์ ์๋ค.
โ๏ธ ์ด์ ํ๊ฒฝ์์๋ ECS, Fargate, EKS ๋ฑ์ ์ปจํ ์ด๋ ์ค์ผ์คํธ๋ ์ด์ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ์ด๋ฐ ๊ฒฝ์ฐ์๋ ํด๋น ์๋น์ค๊ฐ ECR ์ธ์ฆ์ ์๋์ผ๋ก ์ฒ๋ฆฌํด์ฃผ๊ธฐ ๋๋ฌธ์ ๋ณ๋์
docker login์ด ํ์ ์๋ค.
2. ํฌํธ ์ถฉ๋ ์๋ฌ
์๋ฌ ๋ฉ์์ง ์ค ๋ค์๊ณผ ๊ฐ์ ๋ด์ฉ์ด ์์๋ค.
Bind for 0.0.0.0:8080 failed: port is already allocated.
์ด๋ EC2 ๋ด์์ ํฌํธ 8080์ ์ด๋ฏธ ์ ์ ํ๊ณ ์๋ ์ปจํ ์ด๋๊ฐ ์คํ ์ค์ด๋ผ๋ ๋ป์ด๋ค. ๋๋ถ๋ถ ์ด์ ์ ์คํ๋ ์ปจํ ์ด๋๊ฐ ์ข ๋ฃ๋์ง ์์์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค.
๋ฐ๋ผ์ docker run์ ํ๊ธฐ ์ ์ ๊ธฐ์กด ์ปจํ
์ด๋๋ฅผ ์ ๋ฆฌํด์ฃผ๋ ๋ช
๋ น์ด๋ฅผ ์ถ๊ฐํ๋ค.
docker ps -q --filter ancestor=$ECR_URI | xargs -r docker stop
docker ps -aq --filter ancestor=$ECR_URI | xargs -r docker rm
๐ก
$ECR_URI๋ ECR ์ด๋ฏธ์ง ์ฃผ์๋ค. ํ์ง๋ง ์ด์ ๊ณผ ๋ค๋ฅธ ํ๊ทธ๊ฐ ๋ถ์ ์ด๋ฏธ์ง๊ฑฐ๋ ์ด๋ฆ์ด ๋ฐ๋์๋ค๋ฉด ํด๋น ํํฐ์ ์ ์กํ ์ ์๋ค.
๋ ์์ ํ๊ฒ ํ๋ ค๋ฉด ์ปจํ ์ด๋ ์ด๋ฆ์ ๋ช ์ํด์ ์ ๋ฆฌํ๋ ๊ฒ๋ ๊ณ ๋ คํ ์ ์๋ค.
docker stop my-app-container || true
docker rm my-app-container || true
์ ์์ ์ผ๋ก ํฌํธ๊ฐ ๋น์์ก๋์ง ํ์ธํ๋ ค๋ฉด ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ๋ค.
sudo lsof -i :8080
๋ฐฐํฌ๋ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์์ง๋ง, health check๋ ๊ณ์ํด์ ์คํจํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋ผ์ ์ฌ๋ฌ๊ฐ์ง ๊ฐ๋ฅ์ฑ์ ์ฒดํฌํด๋ณด๋ฉฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.

๋ค์ ๋ช ๋ น์ด๋ค์ ํตํด ์คํ ์ํ ๋ฐ ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์๋ค. ์ด ๋จ๊ณ์์ ์ปจํ ์ด๋๊ฐ ์ ๋๋ก ์คํ๋์ง ์์๊ฑฐ๋, ์คํ๋์๋ง์ ๊บผ์ง๋ ๊ฒฝ์ฐ์ ๋ก๊ทธ์์ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ฐพ์์ผ ํ๋ค.
docker ps # ํ์ฌ ์คํ ์ค์ธ ์ปจํ
์ด๋ ๋ชฉ๋ก
docker ps -a # ์คํ ์ด๋ ฅ์ด ์๋ ๋ชจ๋ ์ปจํ
์ด๋
docker logs <์ปจํ
์ด๋ ID> # ์ฃฝ์ ์ปจํ
์ด๋์ ๋ก๊ทธ๊น์ง ํ์ธ
์ปจํ
์ด๋๊ฐ ์ ์ ์๋ํ๊ณ ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๋๋ฐ๋ health check๊ฐ ์คํจํ๋ค๋ฉด,build.gradle์ Spring Actuator ์์กด์ฑ์ด ์ ๋๋ก ์ถ๊ฐ๋์๋์ง ํ์ธํ๋ค.
implementation 'org.springframework.boot:spring-boot-starter-actuator'
๋ํ ๋ฐฐํฌ ์ ์ฌ์ฉ๋๋ application-prod.yml์ ๋ค์ ์ค์ ์ด ํฌํจ๋์ด ์๋์ง ํ์ธํ๋ค.
management:
endpoints:
web:
exposure:
include: health,info
์ ์ค์ ๋ค์ด application-prod.yml์ ํฌํจ๋์ด ์๋ค๋ฉด, ํด๋น ํ๋กํ์ผ์ด ์ค์ ๋ก ํ์ฑํ๋๊ณ ์๋์ง๋ ๋ฐ๋์ ํ์ธํด์ผ ํ๋ค. Spring Boot ์ฑ์ ์คํํ ๋ ๋ค์๊ณผ ๊ฐ์ด prod ํ๋กํ์ผ์ด ํ์ฑํ๋์ด์ผ ์ค์ ์ด ์ ์ฉ๋๋ค.
-Dspring.profiles.active=prod
๊ธฐ์กด์๋ docker run ๋ช
๋ น์ด์์ ์ง์ ์ ๋ฌํ์ง๋ง, Dockerfile ๋ด ENTRYPOINT๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํด ๊ณ ์ ํ๋ ๋ฐฉ์์ผ๋ก ๊ฐ์ ํ๋ค.
ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "app.jar"]
์ค์ ์ด ์ ๋์ด ์๋๋ฐ๋ ์ฌ์ ํ health check๊ฐ ์คํจํ๋ค๋ฉด, ๋ณด์ ์ค์ (SecurityConfig)์ ์ ๊ฒํด์ผ ํ๋ค. EC2์ SSH ์ ์ํด ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํด ํ์ธํด๋ดค๋ค.
curl http://localhost:8080/actuator/health
ํด๋น ๋ช
๋ น์ด๋ฅผ ์คํํ์ ๋ ์๋์ฒ๋ผ 403 Forbidden ์๋ต์ด ์ค๋ฉด, actuator endpoint๋ ์ด๋ ค ์์ผ๋ ์ ๊ทผ ๊ถํ์ด ๋งํ ์๋ค๋ ๋ป์ด๋ค.

๋, SecurityConfig ํด๋์ค์์ actuator ๊ฒฝ๋ก๋ ํ์ฉํด์ผ ํ๋ค.

EC2 ๋ด๋ถ์์๋ health ์๋ต์ด ์ ์ค๋๋ฐ๋ ์ธ๋ถ health check๊ฐ ์คํจํ๋ค๋ฉด, EC2 ๋ณด์ ๊ทธ๋ฃน ์ค์ ์ ์ ๊ฒํด์ผ ํ๋ค. AWS๋ฅผ ์ฌ์ฉํ ๋๋ ํนํ๋ ๋ณด์ ๊ทธ๋ฃน ์ค์ ๋ฌธ์ ์ธ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ ์ ํ์ธํด๋๋ ๊ฒ์ด ์ข๋ค. ํฌํธ 8080์ด ํผ๋ธ๋ฆญ์ผ๋ก ์ด๋ ค ์์ด์ผ ์ธ๋ถ์์ ์ ๊ทผ ๊ฐ๋ฅํ๋ค.

EC2์์ ๋ค์ ํ์ธํด๋ณด๋ ์ ์์ ์ผ๋ก HTTP/1.1 200 OK ์๋ต์ด ์จ ๊ฒ์ ๋ณผ ์ ์๋ค.

๋ค์๊ณผ ๊ฐ์ด ์น ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด์๋ ๋ณผ ์ ์๋๋ฐ, http://<EC2 ํผ๋ธ๋ฆญ IPv4 ์ฃผ์>:8080/actuator/health ์ ๊ฐ์ด ์
๋ ฅํด์ผ ํ๋ค. ๋๋ EC2์ ํ๋ ฅ์ IP๋ฅผ ํ ๋นํ์ฌ ๊ณ ์ ๋ ์ฃผ์๋ก ํญ์ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก ์ฐ๊ฒฐํ๋ค.

๋ฐฐํฌ ํ
์คํธ๊ฐ ์ ์์ ์ผ๋ก ๋๋ฌ๋ค๊ณ ์๊ฐํ์ง๋ง, ์ดํ ํ
์คํธ ์ค Redis ์ฐ๊ฒฐ ๊ด๋ จ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.

๋ก๊ทธ๋ฅผ ์์ธํ ๋ณด๋ฉด Redis ๊ด๋ จ ์ค๋ฅ๋ผ๋ ๊ฒ์ ์ ์ ์๋ค.
Caused by: io. lettuce. core. RedisConnectionException: Unable to connect to
eightyage-cache. 2i7efi.ng. 0001.apn2. cache. amazonaws. com/<unresolved>: 6379
application-prod.yml์๋ ์๋์ ๊ฐ์ด Redis ์ค์ ์ ํด๋์๋ค.
data:
redis:
host: ${REDIS_HOST}
port: 6379
๊ทธ๋ฆฌ๊ณ REDIS_HOST ํ๊ฒฝ ๋ณ์๋ GitHub Repository Secrets์ ์ ์์ ์ผ๋ก ๋ฑ๋กํ๋ค. ์ด ํ๊ฒฝ ๋ณ์์๋ ElastiCache Redis ์ธ์คํด์ค์ ์๋ํฌ์ธํธ๊ฐ ๋ค์ด๊ฐ๋ค.

EC2์์ telnet์ผ๋ก Redis์ ์ ์์ ์๋ํด๋ดค๋ค.
telnet <REDIS_HOST> 6379
ํ์ง๋ง ๋ค์๊ณผ ๊ฐ์ด ์ฐ๊ฒฐ์ด ๋์ง ์์๋ค.

ํด๋น ๋ฌธ์ ๋ ElastiCache Redis ์ธ์คํด์ค์ ๋ณด์ ๊ทธ๋ฃน์ด EC2์ ์ ๊ทผ์ ํ์ฉํ์ง ์์์ ๋ฐ์ํ ๊ฒ์ด์๋ค. Redis๊ฐ ํผ๋ธ๋ฆญํ๊ฒ ์ด๋ฆฌ์ง ์๊ธฐ ๋๋ฌธ์, EC2์ฒ๋ผ ๋ด๋ถ ํต์ ์ ํ๋ ค๋ฉด ๋ณด์ ๊ทธ๋ฃน ๊ฐ ์ธ๋ฐ์ด๋ ํ์ฉ ์ค์ ์ด ํ์๋ค. ๋ค์๊ณผ ๊ฐ์ด ์ธ๋ฐ์ด๋ ๊ท์น์ EC2 ์ธ์คํด์ค์ ๋ณด์ ๊ทธ๋ฃน์ ์์ค๋ก ์ถ๊ฐํด์ค๋ค.

๐ ๋ณด์ ๊ทธ๋ฃน ๊ฐ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ๋ ์ด์ : EC2 ์ธ์คํด์ค์ IP๋ ๋ณํ ์ ์์ผ๋ฏ๋ก, ๊ณ ์ ๋ IP๊ฐ ์๋ EC2๊ฐ ์ํ ๋ณด์ ๊ทธ๋ฃน ์์ฒด๋ฅผ ์ฐธ์กฐํ๋ ๋ฐฉ์์ด ์ ์ง๋ณด์์ ์ ๋ฆฌํ๋ค.
์ค์ ๋ณ๊ฒฝ ํ ๋ค์ ์ ์์ ์๋ํด๋ณด๋ ์ ์์ ์ผ๋ก ์ฐ๊ฒฐ์ด ๋์๋ค.

Spring Boot Actuator์์๋ Redis ๊ด๋ จ ํญ๋ชฉ์ด ํฌํจ๋ health ์ ๋ณด๊ฐ ์ ์์ ์ผ๋ก ์ถ๋ ฅ๋์๋ค.
