Server/Docker & Kubernetes

쿠버네티스 정복기 (2) ingress, ingress controller 및 externalName 사용해서 서비스 연결하기

TerianP 2024. 7. 31. 23:56
728x90

1. 시작하면서

벌써 지난 글로부터 1달이 넘게 지났습니다. 이번달에는 정말 뭘 했는지 모르게 빠르게 지나갔네요. 조금의 변명을 하자면, 사실 최근에 회사일이 정말...정말 많았습니다. 원래 이렇게 바쁜 회사도 아니고 나름의 워라벨이 있는 회사였는데 지난 6월부터 시작해서 최근까지도 야근하는건 일상 다반사고 2주에 한번꼴로 주말출근에 때로는 새벽까지 작업하는 일도 있었습니다. 지난번에 적었던 '과연 내가 개발자에 맞을까?' 가 중요한게 아니라 '내 몸이 더 버틸 수 있을까?' 를 생각하게 만드는 한달이었던 것 같네요.

 

물론 이렇다고 제 프로젝트를 때려칠 정도로 아무것도 안한 것은 아니었습니다. 이전에 이야기했듯 최근에는 혼자의 프로젝트가 아닌 팀 프로젝트로 진행하는 만큼 서버에 이것저것 올리고하면서 개발도 개발이지만 쿠버네티스를 더 많이 공부했습니다. 오늘은 그 중에서도 이전부터 계속 꼭! 사용해보자 라고 다짐했었던 ingress 와 ingress controller 을 공부한 내용과 external name 서비스에 대한 이야기를 적을까 합니다.

2. Ingress 너는 누구야?

1) Igress 는 무엇일까?

 Ingress는 클러스터 외부에서 내부로 접근하는 트래픽을 관리하는 API 오브젝트이다. 쉽게 말해, 외부에서 쿠버네티스 클러스터 내의 서비스를 어떻게 접근할지 정의하는 역할을 한다. 즉 외부에서 내부로 요청을 보낼 때 요청을 '어느 서비스의 어느 파드로 보낼지' 를 결정해는 오브젝트가 바로 Ingress 이며 동시에 HTTP 및 HTTPS 트래픽을 라우팅하며, 다양한 규칙을 통해 트래픽을 적절한 서비스로 전달한다.

이는 쿠버네티스에 서비스가 많이 올라가면 올라갈수록 그리고 그에 해당하는 파드가 많으면 많을수록 더욱더 트래픽이나 접속 등 많은 부분에서 아주 중요한 역할을 한다.

2) Ingress 가 필요한 이유

일반적으로 서비스 - 디플로이먼트 - 파드로 이루어진 쿠버네티스에 올라간 하나의 서비스를 외부에서 접속할 수 있게 배포하는 가장 쉬운 방법은 서비스를 NodePort 로 배포하고, 외부에서는 노드의 port 를 통해서 접속할 수 있게 하는 것이다. 그런데 만약 서비스가 5개가 된다면 어떨까? 만약 10개라면...? 만약 50개라면...? 각각 모든 서비스마다 nodeport 를 부여하고 이를 통해서 외부에서 접속해야할까? 물론 단순히 접속을 테스트하고 공부하기 위한 환경에서라면 이렇게해도 무방하다. 다만 실제 운영 환경에서는 보안적으로도 관리적으로 엄청난 이슈가 있다. 매우 귀찮다. 

그러나 인그레스를 사용하는 순간부터 이러한 고민은 의미없는 고민이 되버린다. 인그레스는 역할은 "요청"을 처리하는 역할이다. 앞서 이야기한 트래픽을 관리하는 것에서 끝나는 것이 아니라 우리가 NodePort 로 요청을 해야하던 것에서 벗어나서 특정한 서비스에 대한 요청을 url path 를 통해서 할 수 있도록 만들기 때문이다!

이렇게 많은데...하나하나...?

3) path 가 곧 접속 통로

nodeport 로 서비스를 잡게되면 실제 서비스에 접속하기 위해서는 아래의 주소를 사용한다.

chatforyou 서비스 : 192.168.0.1:8443
kurento 서비스 : 192.168.0.1:8444
turn 서버 서비스 : 192.168.0.1:8445
jenkins 서비스 : 192.168.0.1:8446

 

문제는 여기서 발생한다. 각각의 서비스에 요청을 보낼때마다 매번 포트를 다르게 해서 보내야한다. 이는 곧 외부에서 요청을 받아서 처리하는 순간 외부에 노출되는 포트가 총 4개가 되버리는 문제가 발생한다는 것이다. 동시에 "열려있는 포트" 가 외부에 노출되는 문제가 발생한다.

 

그러나 ingress 로 바꾸게 되면 엄청난 일이 발생한다. 모든 서비스가 하나의 포트로 통하게 되고, 외부에서는 단순히 path 를 조작해서 요청을 관리하게 된다. 여기서 path 란 url path 를 의미한다.

이렇게 하면 ingress 는 443 포트로 들어온 /chatforyou 요청에 대해서 chatforyou 서비스로 연결해주는 역할을 한다. 이는 쿠버네티스의 기능이기에 서비스 내에 구동되는 파드가 N 개라면 요청 트래픽을 N 개에 적절히 분산해서 처리해주는 기능도 함께 한다. 물론 이를 제어하는 방법또한 존재한다.

chatforyou 서비스 : 192.168.0.1:443/chatforyou
kurento 서비스 : 192.168.0.1:443/kurento
turn 서버 서비스 : 192.168.0.1:443/coturn
jenkins 서비스 : 192.168.0.1:443/jenkins

 

그림으로 보면 요렇게? 출처 : https://lordofkangs.tistory.com/566

 

포인트!!
중앙집중화된 트래픽 관리: 하나의 Ingress를 통해 여러 서비스를 관리할 수 있다
라우팅 규칙: 특정 경로나 도메인에 따라 트래픽을 다른 서비스로 라우팅할 수 있다
SSL/TLS 종료: Ingress는 SSL/TLS 인증서를 관리하여 안전한 HTTPS 트래픽을 제공할 수 있다

 

3. 그래서 어떻게 사용하면 될까? - nginx ingress controller

1) Nginx Ingress Controller

ingress 를 사용하는 가장 쉬운 방법은 nginx ingress controller 를 사용하는 것이다. 

Nginx Ingress 컨트롤러: 쿠버네티스 클러스터 내에서 Ingress 리소스를 모니터링하고, 이를 Nginx 설정으로 변환하여 적용한다.
즉, Ingress 규칙을 실제로 구현하고 관리하는 역할을 담당한다!

 

2) 배포하기

Nginx Ingress Controller 설치

Nginx Ingress Controller는 다양한 방법으로 설치할 수 있지만, 공식적으로 제공하는 YAML 파일을 통해 간단히 배포할 수 있다.

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
 

3) 배포 확인하기

nginx ingress controller 가 제대로 배포되었는지 확인하기 위해서는 아래의 명령어를 사용한다.

kubectl get pods -n ingress-nginx

 

dashboard 에서는 요렇게 나오게 된다. 물론 namespace 는 다를 수 있다

이름이 왜 ingress-ingress-nginx-controller 가 되었는지 모르겠어요ㅠㅠ

 

4) Service 리소스

설치가 완료된 후에는 service 리소스에 아래처럼 관련된 service 가 올라온 것을 알 수 있다. 이때 admission 이 아닌 nginx-ingress-controller 에 대한 설정만 해주자

포트의 종류는 ClusterIP 대신 NodePort 로 설정해 둔 후 외부에서 해당 포트로 트래픽이 연결 될 수 있게 만들어주어야 한다.

 

이를 통해서 외부에서 접속하는 443 포트는 노드의 30133 으로 들어올 것이고 30133 은 nginx-ingress-controller 파드의 443 과 포트포워딩 되어있다. 최종적으로는 외부의 443 은 ingress-controller 파드의 443 과 연결되고 트래픽이 흘려들어올 것이다.

 

5. ingress 와 External name service

당연하게도? 기본적으로 인그레스는 같은 namespace 안에있는 서비스를 연결하여 동작한다. 이는 일반적으로 하나의 namespace 에 파드, 디플로이먼트, 서비스, 스토리지 클래스 등 쿠버네티스의 여러 리소스들을 하나로 그룹핑하여 사용하기 때문이다.

이는 인그레스 역시도 마찬가지인데 nginx 라는 namespace 를 지정한 후 여기에 nginx-ingress-controller 가 있다면 일반적으로 nginx 라는 namespace 에 파드와 서비스를 배포해야 nginx-ingress-controller 에 묶어서 사용 가능하다.

 

하지만 당연하게도! nginx 라는 namespace 에 포함되지 않는 다른 namespace 에 배포된 서비스, 파드, 인그레스를 만들어서 연결하고 싶은 경우가 있을 것이다. 예를 들면 kurento 와 나의 프로젝트인 chatforyou 같은 경우 nginx 라는 namespace 가 아닌 각각 kurento-media-server 와 chatforyou 라는 namespace 에 배포되어있다.

그렇다면 이때는 ingress-controller 를 사용하여 각 서비스에 연결은 불가능한걸까? 즉 아래 사진처럼 coffee 와 tea 가 아닌 다른 namespace 에 있는 ade 는 ingress 리소스를 만들어도 의미가 없는 것일까? 굳이 default 라는 namespace 에 배포해줘야하는 것일까? 정답은 당연히 아니다

default 와 namespace-a 는 서로 다른 namespace 이다

 

1) External name 을 통해 외부 서비스 이용하기

ExternalName 서비스는 쿠버네티스 클러스터 외부에 위치한 서비스를 클러스터 내부의 DNS 시스템을 통해 참조할 수 있도록 해주는 서비스 유형입니다. 이는 특정 클러스터 내부의 다른 서비스에서 외부 서비스에 접근할 때, 마치 내부 서비스처럼 사용할 수 있게 해준다.

예를들어 default 와 namespace-a 가 서로 다른 namespace 에 있지만 default namespace 안에 External name 서비스를 사용하여 namespace-a 의 ade 와 연결해둔다면 쿠버네티스 클러스터 안에서는 같은 내부 클러스터라고 인식하여 ade 와 연결되어 정상 동작하게 된다.

 

2) External Name 의 동작방식

쿠버네티스의 DNS 시스템에서는 서비스와 파드를 서로 연결하기 위해 FQDN(정규화된 도메인 이름, Fully Qualified Domain Name) 을 사용한다. 구체적으로는 다음과 같은 구조를 따른다.

<서비스 이름>.<네임스페이스 이름>.<서비스 도메인>.cluster.local

- <서비스 이름>: 클러스터 내에서 정의된 서비스의 이름
- <네임스페이스 이름>: 해당 서비스가 속해 있는 네임스페이스의 이름.
- <서비스 도메인>: 리소스의 유형을 나타내며, 서비스의 경우 svc가 사용된다
- cluster.local: 클러스터 내부의 기본 도메인 이름, 이는 기본적으로 클러스터의 내부 네트워크 도메인을 의미!

 

3) External Name 서비스 리소스 만들기

External Name 리소스는 아래처럼 구성하여 생성 가능하다. 여기서 type 과 externalName, ports 부분이 아주 중요한 포인트이다.

특히 externalName 의 구조를 자세히 살펴보면 앞서 2) 에서 이야기한 FQDN 의 구조를 따르고 있음을 알 수 있다. 즉 아래의 externalName 서비스는 chatforyou-io-servivce 라는 서비스를 사용하고 있으며, 이때 해당 서비스는 chatforyou-io 라는 namepsace 에 존재한다.  또한 svc 는 쿠버네티스 상의 서비스 리소스임을 의미하며, cluster.local 는 클러스터 내부의 기본 도메인 이름이다. port 의 경우 8443 은 chatforyou-io-service 와 연결하기 위한 포트를 지정한다.

apiVersion: v1
kind: Service
metadata:
  name: chatforyou-io-svc
  namespace: nginx
spec:
  type: ExternalName
  externalName: chatforyou-io-service.chatforyou-io.svc.cluster.local
  ports:
  - name: http
    port: 8443

 

1. chatforyou-io-service
chatforyou-io-service 는 쿠버네티스 클러스터 내에서 정의된 서비스의 이름!

2. chatforyou-io
chatforyou-io 는 해당 서비스가 속해 있는 네임스페이스의 이름을 나타낸다.

3. svc
svc 는 해당 리소스가 쿠버네티스의 서비스임을 나타낸다. 이는 서비스 유형의 리소스를 식별하기 위해 사용되며, 쿠버네티스 DNS 시스템에서 서비스 레코드로 등록된다

4. cluster.local
cluster.local 은 클러스터 내부의 기본 도메인 이름이다. 쿠버네티스 클러스터의 내부 네트워크에서 사용되는 기본 도메인으로, 외부에서 이 도메인은 직접적으로 접근할 수 없다. 이 기본 도메인은 클러스터 설정 시 다른 이름으로 변경할 수 있으며, 클러스터의 DNS 시스템 내에서만 유효하다.

 

4) Ingress 와 External Name 연결하기

이제 앞서 만든 External Name 서비스와 Ingress 를 연결해볼 시간이다. Ingress 는 아래처럼 생성한다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: chatforyou-io-ingress
  namespace: nginx
  annotations:
    nginx.ingress.kubernetes.io/app-root: /
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/session-cookie-name: route
    nginx.ingress.kubernetes.io/use-regex: 'true'
spec:
  ingressClassName: nginx
  rules:
    - host: myproject.project.io
      http:
        paths:
          - path: /chatforyouio(/|$)(.*)
            pathType: Prefix
            backend:
              service:
                name: chatforyou-io-svc
                port:
                  number: 8443

 

nginx.ingress.kubernetes.io/app-root 어노테이션은 사용자가 특정 URL로 접근했을 때 자동으로 애플리케이션의 루트 경로로 리디렉션한다. 예를 들어, 사용자가 http://myproject.project.io 에 접속할 때 http://myproject.project.io/ 로 리디렉션하도록 설정할 수 있다. 이는 주로 애플리케이션의 초기 화면이나 특정 시작 페이지로 사용자를 안내하는 데 유용하다.

nginx.ingress.kubernetes.io/rewrite-target: rewrite-target 은 요청 URL의 경로를 다른 경로로 변경하는 데 사용한다. 경로 path 에 정규식을 넣어서 사용할 수 있다. 예를 들어 $2 로 해둔다면 사용자가 정의한 경로의 두 번째 캡쳐 그룹을 나타낸다. 여기서 /chatforyouio 는 첫번째 그룹을, $2 에 해당하는 두번째 그룹은 /chatforyouio/{{여기}} 이다. 이 어노테이션은 http://myproject.project.io/chatforyouio/test 요청을 http://myproject.project.io/test로 라우팅하고자 할 때 사용할 수 있다.

nginx.ingress.kubernetes.io/use-regex: rewrite-target 어노테이션을 사용하며 정규식을 사용한다면 꼭 필요한 어노테이션이다. 경로 path 에 정규식을 넣고 이를 활용할 수 있게 한다.

nginx.ingress.kubernetes.io/session-cookie-name: 세션 고정(session affinity)을 위해 쿠키 이름을 설정한다. 특정 사용자가 세션을 특정 백엔드 파드에 고정시키기 위한 쿠키를 사용할 때 유용하다. route 라는 이름의 쿠키를 설정하여, 동일한 사용자가 항상 같은 파드에 연결될 수 있도록 한다.

 

잘! 만들어졌다

 

5) chatforyou-io 서비스로 요청하기

이제 드디어 실제 서비스로 요청할 시간이다. 내가 요청할 서비스는 chatforyou-io 이고, 아래의 api 이다. 아주 간단한 api 로 각각 유저 정보를 받아서 저장하고 삭제하는 api 이다. 여기서 중요한 것은 이 api 의 실제 요청 주소는 /chatforyouio/user/create 가 아닌 /user/create 이다. 즉 앞서 Ingress 로 설정한 것처럼 외부에서의 요청은 chatforyouio/user/create 로 하되 내부 트래픽은 /user/create 로 가야한다는 것이다.


@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    @PostMapping("/create")
    public ResponseEntity<Map<String, Object>> createUser(@RequestBody UserVO user) throws BadRequestException {
        Map<String, Object> response = new HashMap<>();
        UserInfo userInfo = userService.saveUser(user);
        response.put("result", "success");
        response.put("userData", userInfo);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }
    
    @DeleteMapping("/delete")
    public ResponseEntity<Map<String, Object>> deleteUser(@RequestBody UserVO user) throws BadRequestException {
        Map<String, Object> response = new HashMap<String, Object>();
        boolean result = userService.deleteUser(user);
        if (result) {
            response.put("result", "success");
            return new ResponseEntity<>(response, HttpStatus.OK);
        } else {
            response.put("result", "fail delete user");
            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
        }
    }
}

 

이제 실제 요청은 수행한다. 요청은 postman 으로...!! 이제 이전과는 다르게 {요청주소}:{포트}/{path} 가 아닌 {요청주소}/{path} 가 된다. 요청은 총 2번 수행할 것이며 각각 create 생성, delete 삭제 이다. 삭제시에는 확인을 위해 비밀번호는 받는데 이때 비밀번호를 틀리게하여 에러를 제대로 받을 수 있는지도 테스트 할 것이다.

오오 회원가입 성공 오오
오오 에러도 잘 나와용!

 


3. 이제 다음은...?

정말 오랜만에 포스팅을 남겨보았습니다. 동시에 보면 알겠지만 본격적으로 chatforyou V2 즉 chatforyou.io 프로젝트가 진행되고 있음을 확인할 수 있을 것입니다. 사실 유저 회원가입이나 인증 이메일 발송 등 간단한 부분은 이미 개발이 거의 끝나가고 있습니다. 앞으로는 친구 기능, 방 생성, 입장 등 여러 이벤트가 있지만...그래도 계속 발전하고 진행되고 있습니다ㅎㅎ 살짝 맛보기로 대시보드라도 업로드하고 싶지만 일단은 조금 남겨두는걸로ㅋㅋㅋ

 

담 포스팅은 아마 openvidu 및 redis 배포와 관련된 내용이나 인증메일을 보내는 부분과 관련된 포스팅이 될 것입니다. 화이팅!!

 

 

Reference

https://beann.tistory.com/entry/Ingress%EC%97%90%EC%84%9C-%EB%8B%A4%EB%A5%B8-Namespace%EC%97%90-%EC%9E%88%EB%8A%94-Service%EB%A1%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85-%ED%95%98%EA%B8%B0

 

Ingress에서 다른 Namespace에 있는 Service로 라우팅 하기

기본적으로 Kubernetes에서 Ingress는 Namespace에 종속적입니다. 따라서 A-namespace에 있는 Ingress는 B-Namespace에 있는 Service로 라우팅을 하지 못하는데요. 이를 해결하기 위해서는 External Name을 이용하면 됩

beann.tistory.com

https://it-sol.tistory.com/16

 

23.9.4(월) 쿠버네티스 7일차

https://kubernetes.io/ko/docs/concepts/services-networking/ingress/ 인그레스(Ingress) URI, 호스트네임, 경로 등과 같은 웹 개념을 이해하는 프로토콜-인지형(protocol-aware configuration) 설정 메커니즘을 이용하여 HTTP (

it-sol.tistory.com

https://jangcenter.tistory.com/127

 

[Kubernetes] Ingress(Controller)

Ingress란 서비스 앞에 있는 로드벨런스로 클러스터로 유입된 외부의 트래픽을 적절한 마이크로서비스로 라우팅 사용자가 지정한 특정 규칙에 따라 트래픽을 다양한 서비스로 전달하는 Controller

jangcenter.tistory.com