이번 포스팅은 EKS 환경에서 NodeJS 이미지를 배포하려고 할 때 발생하는 문제점들을 기록하고 해결하는 포스팅입니다. 따라서 포스팅을 읽기 전 kubernetes에 대한 기본적인 이해가 바탕이 되어야 합니다. 만약 부족한 설명이나 틀린 지점이 있다면 댓글로 남겨주시길 바랍니다.
아래 애플리케이션 및 환경 구성에 대해 더 자세한 사항은 유데미의 【한글자막】 Docker & Kubernetes : 실전 가이드 강의 중 <Kubernetes - 배포 (AWS EKS)>
섹션에 준비되어 있는 것과 동일합니다. 해당 강의를 듣고 따라 실습하던 중 발생하는 문제점들입니다.
auth-api/Dockerfile
FROM node:14-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "node", "auth-app.js" ]
// /auth-api
docker build -t blcklamb/kub-dep-example-auth .
docker push blcklamb/kub-dep-example-auth
users-api/Dockerfile
FROM node:14-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "node", "users-app.js" ]
// /users-api
docker build -t blcklamb/kub-dep-example-users .
docker push blcklamb/kub-dep-example-users
kubernetes/auth.yaml
apiVersion: v1
kind: Service
metadata:
name: auth-service
spec:
selector:
app: auth
type: ClusterIP
ports:
- protocol: TCP
port: 3000
targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-deployment
spec:
replicas: 1
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
spec:
containers:
- name: auth-api
image: blcklamb/kub-dep-example-auth:latest
env:
- name: TOKEN_KEY
value: "shouldbeverysecure"
kubernetes/users.yaml
apiVersion: v1
kind: Service
metadata:
name: users-service
spec:
selector:
app: users
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: users-service
spec:
selector:
app: users
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-deployment
spec:
replicas: 1
selector:
matchLabels:
app: users
template:
metadata:
labels:
app: users
spec:
containers:
- name: users-api
image: blcklamb/kub-dep-example-users:latest
env:
- name: MONGODB_CONNECTION_URI
value: "mongodb+srv://<username>:<password>@user-database.kaniwpt.mongodb.net/?retryWrites=true&w=majority&appName=User-database"
- name: AUTH_API_ADDRESSS
value: "auth-service.default:3000"
로컬에서 이미지를 빌드한 후 배포하기 전 EKS의 워커 노드도 잘 생성되었는지 확인했습니다.
/kubernetes
kubectl apply -f=auth.yaml -f=users.yaml
그런데 apply를 한 뒤 pod의 상태가 CrashLoopBackOff
가 뜬 것을 확인할 수 있습니다. 더 자세한 에러 발생 원인을 파악하기 위해 log를 확인하니, 두 pod 모두 exec /usr/local/bin/docker-entrypoint.sh: exec format error
라는 로그가 뜬 것을 확인할 수 있습니다.
Forcing docker to use linux/amd64 platform by default on macOS - stack overflow
Docker images built with Apple Silicon (or another ARM64 based architecture) can create issues when deploying the images to a Linux or Windows based *AMD64 environment (e.g. AWS EC2, ECS, etc.). For example, you may try to upload your docker image made on the M1 chip to an AWS ECR repository and it fails to run. Therefore, you need a way to build AMD64 based images on the ARM64 architecture, whether it's using Docker build (for individual images) or docker-compose build (e.g. for multi-image apps running in a docker compose network).
stack overflow의 답변을 요약하자면 이미지가 빌드된 환경과 해당 이미지가 올라가는 환경이 달라서 발생하는 호환성 문제입니다. 이전 docker 실습 때도 종종 일어났던 문제이기 때문에 m1 사용자라면 아키텍쳐의 차이로 인한 문제라고 쉽게 유추할 수 있습니다.
위 참고 링크에 따르면 이에 대한 해결 방법은 두 가지가 있습니다. 전역적으로 docker가 빌드되는 플랫폼을 amd64로 지정해주거나, 빌드할 때마다 Dockerfile에서 플랫폼을 명시해주는 것 입니다. 저는 전역적으로 환경 변수를 변경하면 추후 디버깅이 어려워질 것 같아서, 후자 방식을 선택했습니다. m1 사용자는 이런 호환성 이슈로 간혹 고통받을 때가 있습니다.
auth-api/Dockerfile
# AS-IS
FROM node:14-alpine
#TO-BE
FROM --platform=linux/amd64 node:14-alpine
# 생략
users-api/Dockerfile
# AS-IS
FROM node:14-alpine
#TO-BE
FROM --platform=linux/amd64 node:14-alpine
// 생략
이후 위에 서술한 그대로 rebuild-push-배포(apply yaml)를 진행했습니다. 그리고 아래와 같은 log를 확인할 수 있습니다.
latest
로 하는 경우위 log는 과연 어떤 문제가 있는 것일까요? 핵심은 unchanged
에 있습니다. 처음 배포하거나 업데이트 사항이 있다면 unchanged
가 아니라 configured
라고 뜨게 됩니다.
Kubernetes: kubectl apply does not update pods when using "latest" tag
If nothing changes in the deployment spec, the pods will not be updated for you. This is one of many reasons it is not recommended to use :latest, as the other answer went into more detail on. The Deployment controller is very simple and pretty much just does DeepEquals(old.Spec.Template, new.Spec.Template), so you need some actual change, such as you have with the PATCH call by setting a label with the current datetime.
stack overflow의 내용을 요약하자면, deployment spec에 아무런 변경 사항이 없으면 pod는 갱신되지 않는다는 것입니다. 위 log는 각 yaml 파일의 업데이트 사항이 없기 때문에 이미지가 registry로 새롭게 push되었다 한들 새로운 이미지를 받아오지 않았음을 의미합니다.
참고 링크에서 이야기하듯이 :latest
태그 사용을 하지 않으면 됩니다. 그래서 각 이미지 별 버저닝을 태그로 지정하여 새로운 이미지를 명시해주면 됩니다.
docker build -t blcklamb/kub-dep-example-auth:v1 .
docker push blcklamb/kub-dep-example-auth:v1
docker build -t blcklamb/kub-dep-example-users:v1 .
docker push blcklamb/kub-dep-example-users:v1
kubernetes/auth.yaml
// Service는 위와 동일
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-deployment
spec:
// replicas, selector, template는 위와 동일
spec:
containers:
- name: auth-api
image: blcklamb/kub-dep-example-auth:v1
// 이하 동일
kubernetes/users.yaml
// Service는 위와 동일
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-deployment
spec:
// replicas, selector, template는 위와 동일
spec:
containers:
- name: users-api
image: blcklamb/kub-dep-example-users:v1
// 이하 동일
kubectl apply -f=auth.yaml -f=users.yaml
이제는 새로운 이미지로 deployment가 갱신된 것을 알 수 있습니다. service는 업데이트한 부분이 없으니 unchanged
인 것이 맞습니다.
오 이런 아직 끝나지 않았습니다. auth-api
의 경우 STATUS
가 Running
으로 잘 동작하고 있으나 user-api
의 경우 다시 한 번 CrushLoopBackOff
가 발생합니다.
Server Discovery And Monitoring engine is deprecated - stack overflow
위 log에 나온대로, 현 애플리케이션에서 사용하고 있는 mongoDB에서 Server Driver가 deprecated되었기 때문에 발생한 문제입니다.
log 말을 잘 들으면 됩니다. mongoose.connect()
에 아래와 같이 option을 추가합니다.
users-api/users-app.js
// AS-IS
mongoose.connect(
process.env.MONGODB_CONNECTION_URI,
{ useNewUrlParser: true },
(err) => {
if (err) {
console.log('COULD NOT CONNECT TO MONGODB!');
} else {
app.listen(3000);
}
}
);
// TO-BE
mongoose.connect(
process.env.MONGODB_CONNECTION_URI,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err) => {
if (err) {
console.log("COULD NOT CONNECT TO MONGODB!");
} else {
app.listen(3000);
}
}
);
이후 이미지 rebuild-push-쿠버네티스 yaml 업데이트-kubectl apply 과정을 반복합니다.
문제 사항2에서 말한대로 태그로 버전을 명시하여 변경하는 것도 잊으면 안됩니다.
docker build -t blcklamb/kub-dep-example-users:v2 .
docker push blcklamb/kub-dep-example-users:v2
kubectl apply -f=users.yaml
이제 users pod 또한 잘 생성됐음을 알 수 있습니다.
하지만 여기서 끝나지 않았습니다.
loadbalancer의 url로 요청을 보내도 애플리케이션이 동작하지 않습니다.
현재 구조는 users-api
애플리케이션에서 mongoDB를 향해 DB query를 요청하고 있습니다. 이때 users-api
애플리케이션의 ip 주소는 EC2 load balancer(ELB)에 의해 생성되고, 해당 ip 주소는 ELB 특성 상 끊임없이 변경됩니다. (자세한 사항은 아래 링크 참고하길 바랍니다)
따라서 mongoDB로 요청되는 ip를 전체 ip에서 접근할 수 있도록 설정하면 됩니다.
이제 정상적으로 애플리케이션이 동작하는 것을 알 수 있습니다.
아키텍쳐 차이로 인한 문제와 함께 강의에서 제공해준 애플리케이션이 현재와 버전 차이가 나 발생한 문제가 많은 것 같습니다. 같은 강의와 환경에서 수강하고 있는 분들에게 도움이 되길 바라겠습니다. m1의 호환성 문제는 고질적인 문제라 로컬 환경을 바꾸지 않고 이를 한 번에 해결할 수 있는 툴이나 방법을 고민해야함을 느꼈습니다. docker, kubernetes 실습 시 amd64, arm64를 포함한 에러 로그를 보면 바로 m1 문제인가?
라는 의심부터 하게 됩니다. 다음 macbook은 이러한 호환성 문제가 없는지 확인해야할 것 같습니다.
또한 인프라의 영역은 문제 발생 원인이 워낙 방대하여 많은 것을 꿰고 있어야 함을 알 수 있었습니다. 만약 mongoDB를 사용한 적 없었더라면 마지막 문제에 대한 원인을 파악하기 어려웠을 것 같습니다. 더더욱 다양한 경험이 언젠간 도움이 되리라는 것을 알 수 있었고, 앞으로도 다양한 스택으로 개발 및 배포를 하며 기술을 습득해야겠습니다.