Zero To One

ECS와 EKS에서 컨테이너끼리 어떻게 소통할까? 본문

카테고리 없음

ECS와 EKS에서 컨테이너끼리 어떻게 소통할까?

Zero_To_One 2022. 6. 7. 15:30

0. 아키텍쳐

 

 

1. 배경

프로젝트를 하면서 Dev환경에는 ECS(EC2)를 사용했고, Staging,Production 환경에서는 EKS를 사용했습니다.

둘다 컨테이너 서비스이지만 두개의 차이점은 명확했습니다. 

Network 할당 방법, LoadBalancer 분산방법 등 여러가지의 차이점이 존재하나,

프로젝트를 진행하면서 가장 시간을 많이 들인 부분인 Auth-Server와 Redis를 서로 어떻게 연결할 것인가에 대해 알아보도록 하겠습니다.

여기서 Auth-Server는 Redis를 호스트로 하는 서버입니다.

2. 처음엔 이렇게 생각했습니다

1. ECS 상에서는 하나의 작업정의 안에 2개의 컨테이너 즉, Auth-Server와 Redis를 같이 넣어주면 될 것이다.

 

2. EKS 상에서는 하나의 파드안에 2개의 이미지 즉, Auth-Server와 Redis를 같이 넣어주면 될 것이다. 

 

그러나 1번은 1/2만 맞았고, 2번은 완전히 틀린 가정이였습니다.

그 과정을 회고를 통해 남기도록 하겠습니다.

 

3. Error

[ioredis] Unhandled error event: Error: connect ECONNREFUSED 127.0.0.1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1157:16)

가장 많이 만난 에러입니다.

" ioredis가 host인 Redis를 찾을 수 없다"  라는 오류 메세지 입니다.

에러를 검색해본 결과 대다수의 답변은 "redis-server" 명령어를 사용해서 Redis를 켜라 였습니다.

 

4. local

로컬에서의 테스트는 비교적 무난했습니다.

다음과 같이 Auth-Server는 host값을 localhost 혹은 127.0.0.1로 주고 다른 터미널 창에 redis-server을 띄우기만 하면 연결은 잘 되었기 때문이죠.

 

그러나 문제는 docker-compose로 띄울 때 였습니다.

 

5. Docker-compose

docker-compose란 다중컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구입니다.

일단 Dockefile을 기반으로 이미지를 docker hub에 업로드 하고 docker-compose상에 이미지를 실행시키도록 했습니다.

//docker-compose.yml

version: '3'
services:
  node-app:
    image: vnfmsqkek3/auth-server:latest
    ports:
      - "3005:3005"


  redis-server:
    image: redis
    ports:
      - "6379:6379"

그리고 만난 에러 입니다.

연결이 안되있다고 뜹니다.

 

폭풍 구글링(링크를 저장을 안해놨네요... 아마 저 문장 그대로 검색했을 겁니다 )결과 app.js에 host를 컨테이너 이름으로 줘야한다는 것이였습니다.

 

//app.js
const client = redis.createClient({
  host : "redis-server",
  port: 6379,
})
//docker-compose.yml
version: '3'
services:
  node-app:
    image: vnfmsqkek3/auth-server:latest
    ports:
      - "3005:3005"


  redis-server:
    image: redis
    ports:
      - "6379:6379"

다음과 같이 주고 실행한 결과

 

잘 뜨는 것을 확인 할 수 있었습니다.

 

6. ECS

로컬에서 테스트가 잘 되었으니 ECS에 올려봅니다.

 

그러나 의문점이 있었습니다.

 

로컬에서는 compose로 해줬다고 하지만 ECS상에서는 어떻게 연결해줘야할까 고민했습니다.

 

그래서 무작정 실행해봤습니다.

 

그결과

 

두 컨테이너는 running은 되고 있으나, start -> draning -> stop 이 무한 반복 되었습니다.

 

 

ECS는 EC2를 쓰고 있기 때문에 ssm 접속으로 로그를 확인해보았습니다.

 

docker exec -it 컨테이너이름 커맨드

 

마찬가지로 찾을 수가 없다고 뜹니다

 

마찬가지로 검색을 해보았습니다.

 

검색은 "how to connect Redis and nodejs in ECS?" 입니다.

 

ECS상에서는 작업정으로 Link라는 기능을 써야한다는 것을 알았습니다.

 

그래서 app.js에서 host는 redi를 주고,

const client =  new Redis({
  host : 'redis'
});

작업정의에는 app에 link로 redis를 주었습니다.

 

이번에는 draning이 되지 않고 잘 잡히는 것을 볼 수 있었습니다.

 

7. EKS

EKS에서도 처음엔 링크를 통해 deployment를 생성하면 된다고 생각했습니다.

 

그렇게 잘못된 접근으로 하루를 날려먹고 도저히 안되서 크루분들께 SOS를 청했습니다.

 

힌트를 주셨는데, 최종적으로 쿠버네티스에서 kubectl get all 로 보여야 할 것은 

 

deployment 3개, service 3개, Ingress 1개 라고 말씀해주셨고, service는 NodePort 2개, ClusterIP 1개 라고 말씀해 주셨습니다.

 

애초에 파드는 하나의 기능만 할 수 있는 놈인데 2개를 쑤셔넣을려고 하니....잘못된 생각을 가지고 있었습니다.

 

그래서 드는 생각 왜 ClusterIP랑 NodePort랑 나누어야 할까?를 곰곰히 생각해보았습니다.

 

분명 배웠으니 기록을 남겨놓았기에 블로그를 뒤져봤고 제 블로그 글에서 단서를 찾아낼 수 있었습니다.

 

제가 이해한 바로는 ClusterIP는 바깥에서 접근하는 서비스가 아니기 때문에 Redis 처럼 DB 기능을 하는 것은 clusterIP를 써야겠구나! 입니다.

 

왜 쓰는지는 알았으니 이제 연결하는 방법을 찾아보기로 했습니다.

 

검색은 다음과 같이 검색했습니다. "How to connect Redis cluster from application in Kubernetes cluster?"

 

stackoverflow에서 말하기를, endpoint를 써봐라 였습니다.

 

그래서 Redis Service의 endpoint 주소를 host 값으로 넣어 보았습니다.

 

> kubectl describe service redis-service
Name:              redis-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=redis
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.100.161.238
IPs:               10.100.161.238
Port:              redis  6379/TCP
TargetPort:        6379/TCP
Endpoints:         192.168.115.20:6379
Session Affinity:  None
Events:            <none>

혹은 다음과 같은 명령어로 endpoint 주소를 찾을 수 있습니다.

 

kubectl describe service redis-service

실행은 잘 됩니다.

 

그러나! 문제가 있었습니다.

 

Auth Service를 죽이고 다시 생성하니 앤드포인트가 바껴져 있었습니다.

 

> kubectl get endpoints
NAME         ENDPOINTS           AGE
kubernetes   192.168.49.2:8443   54m
redis        172.17.0.3:6379     22s

 

값이 계속 변하길래 endpoint를 고정시키는 법을 찾아보았습니다.

 

다음과 같이 검색했습니다. "kubernetes endpoint"

 

쿠버네티스 공식홈페이지에서 찾을 수 있었고, IP주소를 고정시켜 주었습니다.

 

//redis-endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
 # the name here should match the name of the Service
 name: redis
subsets:
 - addresses:
     - ip: 원하는 IP 주소
   ports:
     - port: 6379

그리고 app.js 에 고정된 IP값을 호스트로 주었습니다.

 

# auth-server/app.js
const Redis = require('ioredis');
const express = require('express');
//const axios = require('axios');
const bodyParser = require('body-parser');
const port = 3005;



const redis_client = new Redis({
  host : '192.168.115.20',
  port : '6379'
});

이로써 서비스를 껏다 키더라도 endpoint가 바뀌지 않는 것을 볼 수 있었습니다...만!

 

찾은 자료중에 stackoverflow에서 DNS 이름으로 주는 방법도 있다! 라고 설명이 되있었습니다.

 

검색은 다음과 같습니다.

"connect Redis with service in NodeJS in K8s cluster?"

"쿠버네티스 dns 주소"

 

저를 반겨주는 한글 공식문서 덕분에 쉽게 답을 찾을 수 있었습니다.

 

따라서 다음과 같이 DNS 주소를 host로 주었습니다.

const client = redis.createClient({
  host: "redis.default.svc.cluster.local", 
  //서비스이름.네임스페이스.svc.클러스터 내에서 사용가능한 최상위 도메인
  port: 6379,
})

설명을 덧붙이자면,

 " api-service.default.svc.cluster.local "

api-service는 서비스의 이름을 뜻한다.

default는 해당 서비스가 속한 네임스페이스이다.

svc는 서비스를 뜻하며, 접근하고자 하는 리소스를 나타낸다.

cluster.local은 클러스터 내에서 사용 가능한 최상위 도메인이다.

 

간단히 말하면, 접근하고자 하는 서비스의 이름과 네임스페이스만 안다면 이렇게 조합된 도메인을 통해 해당 서비스에 접근할 수 있게 됩니다.

 

8. 마무리

지금까지 컨테이너끼리 소통(?) 하는 법을 글로 써봤습니다.

 

제가 이 주제로 글을 쓴 이유는 앞서 언급했듯이 해결하기 위해 시간을 많이 들였기 때문입니다.

 

프로젝트 하루를 날린적도 있고, 밥먹는 시간 제외하고 자기전까지 휴대폰으로 검색하면서 해결해보고자 노력을 많이 쏟았습니다.

 

해당 문제를 해결해 나가면서 쿠버네티스 환경에 조금이나마 다가갈 수 있는 좋은 시간이였습니다.