
저번 글에서는 EC2, EKS, RDS까지 만들어보았으니 이번엔 웹, 애플리케이션을 배포해볼 차례이다.
아래 링크에서 RDS DB와 연결돼 EKS에 배포될 Spring Boot Sample APP과 CloudFront + S3로 정적 웹 호스팅할 VueJS Sample Project를 받았다.
저번 시간에 생성한 EC2 배스천 호스트에 kubectl, eksctl, helm 명령어를 받아야 한다.
kubectl은 Kubernetes API와 통신 가능한 명령어 도구이고, eksctl은 EKS 생성, 관리에 관한 도구, helm은 쿠버네티스 네이티브 패키지 매니저라고 생각하면 된다.
아래 링크를 통해 모두 설치해주자.
kubectl 명령어를 설치했다면 아래의 명령어로 EKS Kubernetes API와의 통신 정보를 Kubeconfig 파일로 가져오자.
aws eks update-kubeconfig --region ap-northeast-2 --name eks-cluster-ap-northeast-2-app
해당 config 파일은 홈 디렉토리의 .kube/config로 저장된다.
config 파일을 저장했으면 kubectl get po -A을 입력해 정상적으로 통신이 되는지를 확인해보자.

EKS에 애플리케이션을 배포할 방법으로 ArgoCD을 사용해보자.
ArgoCD란 Gitops 패턴을 구현한 Continuous Deployment 도구이며, 쉽게 생각하여 깃으로 쿠버네티스에 배포되는 모든 리소스, 오브젝트를 관리한다고 생각하면 된다.
ArgoCD는 CNCF Graduated Project로 사용성과 안정성을 검증받아 쿠버네티스를 사용하는 많은 회사에서 사용하고 있는 오픈 소스 솔루션이다.
공식 문서에 나와있는 대로 배포하자.
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

배포가 다 되었다면 kubectl get po -n argocd을 입력해 정상적으로 파드가 실행되고 있는지 확인하자.

정상적으로 실행되는 것을 확인했다면 이제 ArgoCD 웹으로 접속할텐데 그러기 위해서는 몇 가지 단계를 거쳐야 한다.
아래 문서를 참고해 설치하자.
설치가 완료되었다면 kubectl get po -n kube-system 명령어로 ALC 파드가 정상인지 확인해보자
[dgyoon@ip-10-10-1-141 ~]$ kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
aws-load-balancer-controller-7f784cc58d-4frjd 1/1 Running 0 6m10s
aws-load-balancer-controller-7f784cc58d-6k88w 1/1 Running 0 6m10s
이제 ingress.yaml을 작성하자
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server-ingress
namespace: argocd
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
alb.ingress.kubernetes.io/target-type: instance
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
상세한 명세서는 공식 문서에 전부 나와있고 이 문서만 설명하자면 일단 어노테이션으로 해당 인그레스가 사용할 ALB의 요구 조건을 작성한다.
우리는 인터넷 웹으로 접근할 것이기 때문에 alb.ingress.kubernetes.io/scheme: internet-facing를 추가하고, 헬스체크, 포트는 80 포트로 오픈한다.
alb.ingress.kubernetes.io/target-type는 인스턴스 유형과 파드 IP 유형 두 가지의 선택지가 있는데 파드 IP는 주로 Fargate에서 사용한다고 하므로 우리는 instance로 선택한다.
* 인스턴스 유형의 경우 공개하려는 서비스를 ClusterIP가 아닌 NodePort로 오픈해주어야 한다. *
아래 ingressClassName에는 alb를 넣어주고 하위 스펙들은 기본적으로 /* 경로로 들어온 트래픽을 어느 서비스로 넘겨줄 지 정한다.
ACM과 Route53으로 도메인 SSL 인증서를 발급받은 뒤 어노테이션 alb.ingress.kubernetes.io/certificate-arn에 인증서 ARN을 채워주면 https로 접근 가능하나 이번 실습에서는 생략했다.
ArgoCD 앱에서 TLS 설정을 안하기로 한 경우 관련 컨피그맵을 수정해주어야 한다.
kubectl patch cm -n argocd argocd-cmd-params-cm --type merge -p '{"data":{"server.insecure":"true"}}'
그리고 kubectl rollout restart deploy argocd-server -n argocd 명령어로 서버를 재시작해주자.
잠시 뒤 ALB가 생성되고 인그레스에 할당된 도메인이 나오면 ArgoCD 웹에 접속할 수 있다.

쿠버네티스에 배포하기 위해선 먼저 앱을 컨테이너 이미지로 패키징해야한다.
그리고 컨테이너 이미지를 이미지 저장소에 Push 하여 사용한다.
Terraform에 아래 코드를 추가하고 apply하여 ECR 이미지 저장소를 만들자.
resource "aws_ecr_repository" "ecr_repository" {
name = "dgyoon/springboot"
force_delete = true
}
application.properties에 username, password를 Terraform으로 RDS를 생성할 때 정의한 정보들을 넣어주고, url에는 RDS 클러스터의 라이터 엔드포인트를 넣어준다.
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=myuser
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql: true
그리고 배스천 호스트로 접속해 RDS로 연결한 후 mydatabase 데이터베이스를 생성해준다.

이 후 Sample App을 jar 파일로 빌드한다.
./gradlew clean build
그리고 코드의 최상단 경로에 아래 Dockerfile을 생성한다.
FROM openjdk:17.0.2
WORKDIR /springboot
COPY ./build/libs/accessing-data-mysql-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
CMD ["java","-jar","app.jar"]
그리고 컨테이너 이미지를 빌드한다.
docker build -t [ECR_REPO_ADDRESS]/dgyoon/springboot:latest .
ECR 인증을 마치고 이미지를 Push 한다.
aws ecr get-login-password | docker login --username AWS --password-stdin [ECR_REPO_ADDRESS]
docker push [ECR_REPO_ADDRESS]/dgyoon/springboot:latest
그리고 ArgoCD가 리소스를 생성할 수 있게끔 명시한 Manifest 파일을 생성한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: springboot
namespace: default
labels:
app: springboot
spec:
selector:
matchLabels:
app: springboot
replicas: 1
template:
metadata:
labels:
app: springboot
spec:
containers:
- name: springboot
image: <IMAGE_NAME:TAG>
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 100m
memory: 100Mi
---
apiVersion: v1
kind: Service
metadata:
name: springboot
spec:
selector:
app: springboot
type: NodePort
ports:
- name: springboot
protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: springboot-ingress
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 8080}]'
alb.ingress.kubernetes.io/target-type: instance
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: springboot
port:
number: 8080
여기서는 Deployment, Service, Ingress 리소스만 생성한다.
그리고 이 파일을 Github Repo에 업로드하고 ArgoCD에서 그 Repo와 연결해준다.

Application → NEW APP 후 아래처럼 명시하고 CREATE하면 Application이 생성된다.


Out of Sync 상태이니 Sync를 한번 해주면 아래와 같이 파드, 서비스, 인그레스가 배포된다.


문서에 적혀있는대로 인그레스의 도메인에 /demo/all 붙여 입력해주면 아래와 출력된다.

그리고 CURL로 API 요청을 보내고 다시 /demo/all 을 확인해보면 추가되어 있는 모습을 볼 수 있다.
$ curl http://[API_INGRESS_DOMAIN]/demo/add -d name=First -d email=someemail@someemailprovider.com
SAVED

배스천 호스트에 접속해 RDS DB로 접속 후 확인해보면 레코드가 추가된 것을 볼 수 있다.

이렇게 RDS와 연결된 Spring Boot APP을 EKS에 배포해 API 애플리케이션 서버를 만들어보았다.
이제는 SPA(Single Page Application) Sample을 CloudFront와 S3로 배포해보자.
Terraform으로 CloudFront와 S3 버킷부터 먼저 만들어보자.
지금까지 했듯이 s3 폴더를 만들어 main, variable, output.tf 파일을 작성한다.
modules/s3/main.tf
resource "aws_s3_bucket" "s3_bucket" {
bucket = var.bucket_name
}
modules/s3/variable.tf
variable "bucket_name" {
type = string
}
modules/s3/output.tf
output "bucekt_domain_name" {
value = aws_s3_bucket.s3_bucket.bucket_domain_name
}
output "bucket_name" {
value = aws_s3_bucket.s3_bucket.bucket
}
output "bucket_id" {
value = aws_s3_bucket.s3_bucket.id
}
그리고 cloudfront 모듈도 작성하자.
modules/cloudfront/main.tf
resource "aws_cloudfront_distribution" "cloudfront_distribution" {
origin {
domain_name = var.domain_name
origin_id = var.bucket_id
origin_access_control_id = aws_cloudfront_origin_access_control.s3.id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = var.bucket_id
viewer_protocol_policy = "allow-all"
cache_policy_id = data.aws_cloudfront_cache_policy.cacheoptimized.id
}
viewer_certificate {
cloudfront_default_certificate = true
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
enabled = true
default_root_object = "index.html"
depends_on = [ aws_cloudfront_origin_access_control.s3 ]
}
resource "aws_cloudfront_origin_access_control" "s3" {
name = "cloudfront_s3_oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
data "aws_cloudfront_cache_policy" "cacheoptimized" {
name = "Managed-CachingOptimized"
}
resource "aws_s3_bucket_policy" "allow_cloudfront_access" {
bucket = var.bucket_id
policy = jsonencode({
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::${var.bucket_name}/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "${aws_cloudfront_distribution.cloudfront_distribution.arn}"
}
}
}
]
})
}
modules/cloudfront/variable.tf
variable "domain_name" {
type = string
}
variable "bucket_id" {
type = string
}
variable "bucket_name" {
type = string
}
루트 모듈에서 CloudFront와 S3를 생성해주자.
...
module "s3_web" {
source = "./modules/s3"
bucket_name = "sample-dgyoon-web-bucket"
}
module "cloudfront" {
source = "./modules/cloudfront"
bucket_id = module.s3_web.bucket_id
domain_name = module.s3_web.bucekt_domain_name
bucket_name = module.s3_web.bucket_name
}
...
그리고 VueJS 프로젝트를 생성하고 배포할 수 있게 빌드까지 하자.
npm create vue@latest
cd vue-project
npm install
npm run build
이렇게 하고나면 dist 폴더에 index.html과 js, css 파일이 생길텐데 이 파일들을 전부 방금 생성한 S3 버킷에 전송한다.
cd dist
aws s3 cp ./ s3://sample-dgyoon-web-bucket
그리고 잠시 후 콘솔에 접속해 생성된 CloudFront의 배포 도메인에 접속하면 아래와 같이 나올 것이다.

이렇게 VueJS SPA Sample 웹을 배포해보았다.
최종적으로 완성하게 된 설계도 되겠다.

눈치챈 분도 있겠지만, 주로 EKS에 배포되는 애플리케이션이 백엔드 개발 API 서버가 되고, CloudFront를 통해 배포되는 웹이 프론트엔드 개발 웹 프로젝트가 되겠다.
해당 웹에서 API 서버의 인그레스 도메인을 통해 서로 통신하고 렌더링하면 하나의 웹 서비스 환경을 구축하게 되는 것이다.