쿠버네티스 Network
시작
구성
업무 생성 백엔드 서버, 유저 관리 백엔드 서버, 인증 백엔드 서버 3가지 도메인으로 나뉜 백엔드가 있는 환경으로 구성된 배경을 바탕으로 내용이 진행됩니다.
유저, 인증 서버는 한 파드내 각각의 컨테이너로 작동할 것이고, 파드 내부 통신이 이루어집니다. 업무 생성 서버는 별도의 파드로 구성되며 유저 서버, 업무 생성 서버는 외부에서 이용할 수 있도록 서비스와 연결합니다.
권한의 경우 외부 통신은 하지 않고 유저 API 컨테이너와 동일 파드내 내부 통신을 진행합니다.
compose
로 구성하면 아래와 같습니다.
version: "3"
services:
auth:
build: ./auth-api
users:
build: ./users-api
ports:
- "8080:8080"
tasks:
build: ./tasks-api
ports:
- "8000:8000"
environment:
TASKS_FOLDER: tasks
이제 이 구성을 쿠버네티스로 옮겨봅시다.
users deployment 구성
우선 auth 내부 통신은 제외한 채 구성합니다. 이미지를 빌드하고 docker hub에 push 합니다.
# users-deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-deployment
spec:
# Pod 인스턴스 1개
replicas: 1
selector:
matchLabels:
app: users
template:
metadata:
labels:
app: users
spec:
containers:
- name: users
image: wooglim/kub-demo-users
users service 구성
Pod는 제거되거나 다른 워커노드로 옮겨지면 기본 IP가 변경됩니다. 고정적인 IP를 지원하여 파드와 label로 연결되어 외부로 통신할 수 있게 해주는 service
설정이 필요합니다.
# users-service
apiVersion: v1
kind: Service
metadata:
name: users-service
spec:
selector:
# 파드 label 매칭
app: users
# 실행 노드 관계 없이 모든 포트에 들어오는 요청을 여러 레플리카에 분배
type: LoadBalancer
ports:
- protocol: TCP
# 외부 요청 받는 포트
port: 8080
targetPort: 8080
구성한 YAML 환경을 적용합니다.
kubectl apply -f=users-deployment.yaml -f=users-service.yaml
minikube service users-service
🏃 Starting tunnel for service users-service.
|-----------|---------------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|---------------|-------------|------------------------|
| default | users-service | | http://127.0.0.1:50601 |
|-----------|---------------|-------------|------------------------|
이후 users API 인 로그인, 회원가입 요청을 보낼 수 있겠지만 아직 더미로만 작동할 뿐, auth API와 파드내 내부 통신이 가능하도록 변경해야 합니다.
docker-compose 의 경우 서비스 명으로 같은 네트워크에 포함된 컨테이너라면 이름으로 치환이 가능했지만 쿠버네티스의 경우 다릅니다. 실행중인 환경에 따라 유동적입니다.
기존 users api 서버 코드를 수정 하고 변경사항을 허브에 push 합니다.
// users signup api
// const hashedPW = await axios.get('http://auth/hashed-password/' + password);
const hashedPW = await axios.get(`http://${process.env.AUTH_ADDRESS}/hashed-password/` + password);
이어서 auth api 백엔드 서버를 이미지 빌드 후 도커 허브에 push 합니다.
동일 파드내 존재해야하므로 기존 users-deployment.yaml
에 추가 컨테이너를 설정합니다. auth api의 경우 파드내 통신만 지원할 것이므로 service
를 별도로 설정하지 않습니다.
내부 통신 설정하기
users API에서 auth API를 내부에서 요청할 수 있도록 반영해봅시다.
다시 deployment yaml 파일을 수정합니다.
# users-deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-deployment
spec:
# Pod 인스턴스 1개
replicas: 1
selector:
matchLabels:
app: users
template:
metadata:
labels:
app: users
spec:
containers:
- name: users
image: wooglim/kub-demo-users:latest
env:
- name: AUTH_ADDRESS
# 동일 파드에서 작동하므로 localhost 를 이용해 auth와 연결 auth는 80포트로 서버 실행하므로 생략.
value: localhost
- name: auth
image: wooglim/kub-demo-auth:latest
> kubectl apply -f=users-deployment.yaml
deployment.apps/users-deployment configured
> kubectl get pods
NAME READY STATUS RESTARTS AGE
users-deployment-655d9785dd-gvwxs 2/2 Running 0 29s
users-deployment-88c9f4b57-nw57w 1/1 Terminating 0 25m
deplyoment 설정을 변경하여 내부 통신이 가능하도록 변경됩니다.
다중 deployments 생성
추가로 tasks api 파드를 실행해야 합니다. 또한 외부 노출이 되어야 합니다. 추가로 auth api 와도 연계가 필요하기 때문에
auth api도 결국 별도의 파드로 분리하고 파드내 통신이 아닌 클러스터내 통신으로 변경해야 합니다.
결국 각 api 서버는 별도 파드에 생성되는 그림이 되어야 합니다.
users api deployment
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
image: wooglim/kub-demo-users:latest
env:
- name: AUTH_ADDRESS
value: localhost
auth api deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-deployment
spec:
# Pod 인스턴스 1개
replicas: 1
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
spec:
containers:
- name: auth
image: wooglim/kub-demo-auth:latest
이어서 auth api service를 생성합니다.
apiVersion: v1
kind: Service
metadata:
name: auth-service
spec:
selector:
app: auth
# 클러스터 내부에서만 통신할 것이므로 ClusterIP 타입 설정
type: ClusterIP
ports:
- protocol: TCP
port: 80
targetPort: 80
service의 IP주소 설정하기
service의 IP는 변경되지 않습니다. users 의 연결 내용을 수정하여 auth api 와 내부 통신할 수 있도록 주소를 설정해봅시다.
우선 추가 사항을 반영합니다.
> kubectl apply -f=auth-service.yaml -f=auth-deployment.yaml
service/auth-service created
deployment.apps/auth-deployment created
> kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# 클러스터 내부 통신만 가능
auth-service ClusterIP 10.99.190.110 <none> 80/TCP 33s
이제 위의 고정된 IP로 users api 에서 auth api를 불러오는 부분을 수정해줍니다.
# users-deployment.yaml
- name: AUTH_ADDRESS
# 80포트로 서버 실행하므로 생략.
value: 10.99.190.110
내용을 반영합니다.
> kubectl apply -f=users-service.yaml -f=users-deployment.yaml
service/users-service created
deployment.apps/users-deployment created
> kubectl get pods
NAME READY STATUS RESTARTS AGE
auth-deployment-796bccd658-lmpm5 1/1 Running 0 3m12s
users-deployment-5844949b68-km8hv 1/1 Running 0 13s
자동으로 IP주소 가져오기
쿠버네티스는 서비스를 실행하면 내부적으로 환경변수로 저장합니다. 이를 이용해 다른 서비스의 IP주소를 가져와 봅시다.
auth-service의 경우 내용은 다음과 같았습니다.
# auth-service.yaml
apiVersion: v1
kind: Service
metadata:
name: auth-service
위 서비스 내용 중 메타데이터의 네임을 기준으로 AUTH_SERVICE
가 붙고, 이 서비스의 호스트를 나타내는 SERVICE_HOST
를 합쳐 AUTH_SERVICE_SERVICE_HOST
라는 key로, IP가 value로 생성됩니다.
users api 의 백엔드 코드에서 위 환경변수를 사용하도록 변경합니다.
// users-app.js
const response = await axios.get(
`http://${process.env.AUTH_SERVICE_SERVICE_HOST}/token/` + hashedPassword + '/' + password
);
만일 docker-compose 혹은 내부 백엔드에서 위와 같은 환경병수를 사용하고 있다면 다른 이름으로 사용하도록 검토가 필요합니다.
이미지를 빌드한 후 다시 hub에 push 합니다.
다시 deployment 를 apply
합니다. containers
의 image
에 :latest
로 태그를 설정했으므로 최신의 이미지를 가져오고 대조하게 됩니다.
> kubectl delete -f=users-deployment.yaml
deployment.apps "users-deployment" deleted
> kubectl apply -f=users-deployment.yaml
deployment.apps/users-deployment created
Pod간 통신에 DNS 사용하기
최신 버전의 쿠버네티스는 CoreDNS가 내제되어 있습니다. 클러스터 내부에서 사용할 수 있는 도메인이죠.
> kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
auth-service ClusterIP 10.99.190.110 <none> 80/TCP 13h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 34d
users-service LoadBalancer 10.106.212.21 <pending> 8080:31323/TCP 13h
바로 위 서비스의 NAME 요소들이 클러스터 내부에서 접근 가능한 DNS가 됩니다. 그럼 아래와 같이 설정해도 무방합니다.
# users-deployment
containers:
- name: users
image: wooglim/kub-demo-users:latest
env:
- name: AUTH_ADDRESS
# 80포트로 서버 실행하므로 생략.
# 서비스명.네임스페이스
value: "auth-service.default"
다시 코드로 돌아가 환경변수를 다시 바꿔줍니다.
// users-app.js
const hashedPW = await axios.get(`http://${process.env.AUTH_ADDRESS}/hashed-password/` + password);
이어서 task api 백엔드 코드도 위와 같이 환경변수로 변경한 후 delployment, service yaml을 작성합니다.
# tasks-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tasks-deployment
spec:
# Pod 인스턴스 1개
replicas: 1
selector:
matchLabels:
app: tasks
template:
metadata:
labels:
app: tasks
spec:
containers:
- name: tasks
image: wooglim/kub-demo-tasks:latest
env:
- name: AUTH_ADDRESS
# 80포트로 서버 실행하므로 생략.
value: "auth-service.default"
환경변수를 설정하며 서비스의 경우 외부로 노출이 되어야 하므로 LoadBalancer
로 설정합니다.
# tasks-service.yaml
apiVersion: v1
kind: Service
metadata:
name: tasks-service
spec:
selector:
# 파드 label 매칭
app: tasks
# 실행 노드 관계 없이 모든 포트에 들어오는 요청을 여러 레플리카에 분배
type: LoadBalancer
ports:
- protocol: TCP
# 외부 요청 받는 포트
port: 8000
targetPort: 8000
파드에 여러 컨테이너를 띄우는게 좋을까
대부분의 경우 서로 밀집하게 연관되어 있는 경우를 제외하고는 파드당 여러 컨테이너를 가지려고 하지는 않습니다. 다른 파드의 컨테이너와의 상호작용을 위해서라면 분리하는게 좋죠.
동일 클러스터라면 쿠버네티스에서 자동으로 생성해주는 환경 변수 혹은 services 로, ClusterIP
로 등록되면 coreDNS
를 이용해 서비스명.네임스페이스
로 환경변수로 등록해 상호작용할 수 있죠.