[Docker Mastery] - 16

쿠버네티스 Network

시작

구성

업무 생성 백엔드 서버, 유저 관리 백엔드 서버, 인증 백엔드 서버 3가지 도메인으로 나뉜 백엔드가 있는 환경으로 구성된 배경을 바탕으로 내용이 진행됩니다.

유저, 인증 서버는 한 파드내 각각의 컨테이너로 작동할 것이고, 파드 내부 통신이 이루어집니다. 업무 생성 서버는 별도의 파드로 구성되며 유저 서버, 업무 생성 서버는 외부에서 이용할 수 있도록 서비스와 연결합니다.

권한의 경우 외부 통신은 하지 않고 유저 API 컨테이너와 동일 파드내 내부 통신을 진행합니다.

compose로 구성하면 아래와 같습니다.

yaml
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 합니다.

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

users service 구성

Pod는 제거되거나 다른 워커노드로 옮겨지면 기본 IP가 변경됩니다. 고정적인 IP를 지원하여 파드와 label로 연결되어 외부로 통신할 수 있게 해주는 service설정이 필요합니다.

yaml
# 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 환경을 적용합니다.

bash
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 합니다.

js
// 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 파일을 수정합니다.

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
bash
> 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 설정을 변경하여 내부 통신이 가능하도록 변경됩니다.

volume-error-get

다중 deployments 생성

추가로 tasks api 파드를 실행해야 합니다. 또한 외부 노출이 되어야 합니다. 추가로 auth api 와도 연계가 필요하기 때문에

auth api도 결국 별도의 파드로 분리하고 파드내 통신이 아닌 클러스터내 통신으로 변경해야 합니다.

결국 각 api 서버는 별도 파드에 생성되는 그림이 되어야 합니다.

users api deployment

yaml
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

yaml
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를 생성합니다.

yaml
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 와 내부 통신할 수 있도록 주소를 설정해봅시다.

우선 추가 사항을 반영합니다.

bash
> 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를 불러오는 부분을 수정해줍니다.

yaml
# users-deployment.yaml
- name: AUTH_ADDRESS
  # 80포트로 서버 실행하므로 생략.
  value: 10.99.190.110

내용을 반영합니다.

bash
> 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의 경우 내용은 다음과 같았습니다.

yaml
# auth-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: auth-service

위 서비스 내용 중 메타데이터의 네임을 기준으로 AUTH_SERVICE가 붙고, 이 서비스의 호스트를 나타내는 SERVICE_HOST를 합쳐 AUTH_SERVICE_SERVICE_HOST라는 key로, IP가 value로 생성됩니다.

users api 의 백엔드 코드에서 위 환경변수를 사용하도록 변경합니다.

js
// users-app.js
const response = await axios.get(
  `http://${process.env.AUTH_SERVICE_SERVICE_HOST}/token/` + hashedPassword + '/' + password
);

만일 docker-compose 혹은 내부 백엔드에서 위와 같은 환경병수를 사용하고 있다면 다른 이름으로 사용하도록 검토가 필요합니다.

이미지를 빌드한 후 다시 hub에 push 합니다.

다시 deployment 를 apply 합니다. containersimage:latest로 태그를 설정했으므로 최신의 이미지를 가져오고 대조하게 됩니다.

bash
> 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가 내제되어 있습니다. 클러스터 내부에서 사용할 수 있는 도메인이죠.

bash
> 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가 됩니다. 그럼 아래와 같이 설정해도 무방합니다.

yaml
# users-deployment
containers:
- name: users
  image: wooglim/kub-demo-users:latest
  env:
  - name: AUTH_ADDRESS
    # 80포트로 서버 실행하므로 생략.
    # 서비스명.네임스페이스
    value: "auth-service.default"

다시 코드로 돌아가 환경변수를 다시 바꿔줍니다.

js
// users-app.js
const hashedPW = await axios.get(`http://${process.env.AUTH_ADDRESS}/hashed-password/` + password);

이어서 task api 백엔드 코드도 위와 같이 환경변수로 변경한 후 delployment, service yaml을 작성합니다.

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로 설정합니다.

yaml
# 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를 이용해 서비스명.네임스페이스로 환경변수로 등록해 상호작용할 수 있죠.