반응형

 

2024년 4월 15일

 

 

Kubernetes를 사용하면서 만나는 문제 상황

Kubernetes service resource를 삭제하고 다시 생성하면, Public IP(또는 EXTERNAL-IP) 값이 변경된다.

AWS, Oracle Cloud, Azure, GCP(Google Cloud Platform) 같은 Public Cloud Infra에서 제공하는 

Reserved Public IP 기능과 Service YAML에 'externalIPs' 값을 설정하면 Public IP Address를 고정시킬 수 있지만,

Helm Chart를 작성하거나 Kubernetes Service Manifest 파일을 작성할 때마다 

  1. Reserved Public IP 생성 요청
  2. 위에서 생성된 Public IP 값을 확인 후 Service Manifest 작성(또는 Helm chart) 작성

한다는 것은 참 불편한 작업이다.

 

 

그래서 아래의 기능을 수행하는 Python script를 만들었다.

 

  1. my.yaml 파일에서 Public IP address 정보를 갱신할 'Kubernetes service' 정보와 'Internet domain name' 목록을 설정
  2. DNS 서버에서 직접 Kubernetes API server에게 매번 변경되는 Service resource의 public IP address를 조회 요청
  3. 이렇게 얻은 public IP address(즉, EXTERNAL-IP)를 자동으로 DNS 서버의 Zone file에 반영
  4. BIND9 서비스 데몬을 재기동

 

#!/usr/bin/python3


##############################################################
##
## Written by "Andrew Jeong"
## Date: 2024.03.25
## File: kube-dns-auto-config.py
##############################################################



import os
import time
import socket
import shutil
import subprocess
import yaml
from datetime import datetime
from kubernetes import client, config


##
## File 내용 중에서 일부 문자열을 찾아서 바꿈.
##
def replace_in_file(file_path, old_str, new_str):
    # 파일 읽어들이기
    fr = open(file_path, 'r')
    lines = fr.readlines()
    fr.close()

    # old_str -> new_str 치환
    fw = open(file_path, 'w')
    for line in lines:
        fw.write(line.replace(old_str, new_str))
    fw.close()


##
## Zone file에 있는 Serial 값을 찾기
## NOTE: 주의할 점
##   Zone file 안에 Serial 값이 아래 포맷으로 저장되어 있어야 한다.
##
##   @ IN SOA mydomain.kr root.mydomain.kr (
##     2024032502    ; Serial
##     3600          ; Refresh
##     900           ; Update retry
##     604800        ; Expiry
##     600           ; TTL for cache name server (for 30 minutes)
##   )
##
def search_old_serial(file_path):
    fp = open(file_path)
    lines = fp.readlines()
    fp.close()

    for line in lines:
        # print(f"line: {line}")
        if line.find('Serial') > 0:
            # print(f"I found serial value:  {line}")
            words = line.split(';')
            old_serial = words[0].strip()
            # print(f"old_serial: [{old_serial}]")
    return old_serial



##
## Update 'serial number' of zone file
##
def update_serial_for_zone_file(file_path):
    old_serial = search_old_serial(zone_file)
    curr_date = datetime.today().strftime("%Y%m%d")
    bool_today = old_serial.startswith(curr_date)
    if bool_today is True:
        ## Serial 값이 오늘 한번이라도 Update된 경우
        int_old_serial = int(old_serial)
        new_serial = str(int_old_serial + 1)
    else:
        ## Serial 값이 오늘 처음으로 Update되는 경우
        new_serial = f"{curr_date}00"

    print(f"old_serial: [{old_serial}]")
    print(f"new_serial: [{new_serial}]")

    ## copy file for backup
    bkup_file = f"{zone_file}_{new_serial}"
    shutil.copy(zone_file, bkup_file)

    ## Update serial number
    replace_in_file(zone_file, old_serial, new_serial)



def update_dns(conf):
    kube_svc_name = conf['kube_svc_name']
    kube_domain_name = conf['kube_domain_name']

    print(f"\n[ Checking configuration '{kube_svc_name}' / '{kube_domain_name}' ]")

    for item in svc_info.items:
        if item.metadata.name == kube_svc_name and item.metadata.namespace:
            ip_k8s_svc = item.status.load_balancer.ingress[0].ip
            print(f"  Kubernetes Service External-IP: {ip_k8s_svc}")

    ip_addr_domain_name = socket.gethostbyname(kube_domain_name)
    print(f"  {kube_domain_name}: {ip_addr_domain_name}")

    if ip_addr_domain_name == ip_k8s_svc:
        print("  IP address of domain name is not changed. Nothing to do.")
        return -1
    else:
        update_serial_for_zone_file(zone_file)
        print("  IP address of domain name is changed.")
        print("  Make a backup file of zone file and then")
        print(f"  update '{kube_domain_name} IN A {ip_addr_domain_name}' record")
        print(f"    {ip_addr_domain_name} -> {ip_k8s_svc}")
        replace_in_file(zone_file, ip_addr_domain_name, ip_k8s_svc)
        return 0





## ------------------------------------------------------------------------
##     Begin of Main
## ------------------------------------------------------------------------

## Load configration from YAML file.
with open('/root/WorkSpace/kube-python-client/my.yaml') as fp:
    my_conf_info = yaml.load(fp, Loader = yaml.FullLoader)

zone_file = my_conf_info['zone_file']

##
## Get new IP address from Kubernetes service resource
## and
## Get old IP address from DNS
##

## Configs can be set in Configuration class directly or using helper utility
config.load_kube_config()
v1 = client.CoreV1Api()

while True:
    svc_restart_flag = 0
    svc_info = v1.list_service_for_all_namespaces(watch=False)
    for conf in my_conf_info['services']:
        ret = update_dns(conf)
        if ret == 0:
            svc_restart_flag = 1
    if svc_restart_flag == 1:
        ret_value = subprocess.call("systemctl restart bind9", shell=True)
        print(f"\n\n[ BIND9 is restarted ] Return: {ret_value}\n\n")
    time.sleep(300)

## ------------------------------------------------------------------------
##     End of Main
## ------------------------------------------------------------------------

 

 

아래 예시와 같이 my.yaml 설정 파일을 작성한다.

 

## File: my.yaml

zone_file: /var/cache/bind/mydomain.kr.zone
services:
  - kube_svc_name: almighty
    kube_domain_name: almighty.mydomain.kr
  - kube_svc_name: my-test-server
    kube_domain_name: my-test-server.mydomain.kr

 

참고:
'kube_domain_name'은 실제로 존재하는 domain_name 이어야 한다.
YesNIC, Whois 같은 도메인 등록 기관에서 등록 후 위 테스트를 진행해야 한다.

 

 

 

아래 예시와 같이 실행하면 자동으로 Kubernetes cluster에 있는 Service resource 정보를 반영하여

DNS Zone file이 수정될 것이다.

 

$ ./kube-dns-auto-config.py

[ Checking configuration 'almighty' / 'almighty.mydomain.kr' ]
  Kubernetes Service External-IP: 146.53.17.8
  almighty.mydoamin.kr: 143.26.1.4
  ... 중간 생략 ...

[ Checking configuration 'my-test-server' / 'my-test-server.mydomain.kr' ]
  Kubernetes Service External-IP: 193.13.25.23
  my-test-server.mydomain.kr: 193.13.25.23
  ... 중간 생략 ...
  ...
  ...

 

 

아래와 같이 Kubernetes cluster에 있는 almighty 서비스로 HTTP 트래픽을 보내보면, 트래픽을 잘 갈거다.

$ curl -v almighty.mydomain.kr
... 내용 생략 ...

 

 

 


 

반응형

Public Cloud의 Kubernetes(예: AWS AKS, Azure AKS, Oracle OKE 등)을 사용하다보면,

Public Network에서 들어오는 UDP 트래픽을 Network LB(NLB)가 처리하지 못하는 것처럼 보일 때가 있다.

 

예를 들어 아래와 같은 Service Manifest를 적용했다고 가정해보자.

 

apiVersion: v1
kind: Service
metadata:
  name: almighty
  annotations:
    oci.oraclecloud.com/load-balancer-type: "nlb"  # <- 이 내용을 추가해야 한다.
spec:
  selector:
    app: almighty
  ports:
    - name: myweb
      protocol: TCP
      port: 8080
      targetPort: 8080
    - name: yourweb
      protocol: TCP
      port: 80
      targetPort: 80
    - name: myudp
      protocol: UDP       # <- 테스트를 위해 UDP를 추가한다.
      port: 9090
      targetPort: 9090
  type: LoadBalancer      # <- Service Type을 LoadBalancer로 설정한다.

 

위 Service Manifest를 적용하면, External-IP 항목이 Public IP 값으로 설정되지만 실제로 Pod로 UDP 트래픽이 전달되지 못하는 현상이 생길 것이다.

 

왜 이런 문제가 생길까?

 

Public Network에서 유입되는 UDP 트래픽이 Pod에 전달되지 못하는 정확한 원인은 아래와 같다.

Public Cloud Service 제공사가 제공하는 NLB(Network Load Balancer)는 제대로 UDP 트래픽을 처리한 것이 맞다.

단지, Cloud Infra가 제공하는 K8S Node의 Security Rule에 의해서 UDP 트래픽이 Block되는 것이다.

따라서 Security Rule에 UDP 트래픽이 Pass 되도록 Rule을 추가하기만 하면 된다. (즉, Linux Firewall 설정 하듯이~)

 

AWS AKS, Azure AKS, Oracle OKE 모두 비슷하게 NLB가 UDP 트래픽을 처리하고 Security Rule도 비슷하기 때문에 Oracle의 OCI OKE만 이용해서 설명하면 아래와 같다.

 

아래의 순서로 메뉴/화면을 찾아서 들어간다.

[ Kubernetes Clusters (OKE) ]

-> Clusters 화면에서 특정 Cluster의 VCN을 선택/클릭한다.

-> VCN을 클릭하면, 여러개의 Subnets이 목록이 보여지는데 oke-svclbsubnet-xxxxoke-nodesubnet-yyyy을 선택하여 Security Lists를 확인한다.

-> [ Security Lists ] 화면에 있는 Rule 이름을 선택한다.

-> [ Ingress Rules ]와 [ Egress Rules ] 화면에서 UDP, TCP 트래픽을 모두 허용하도록 Rule을 추가한다.

     예를 들어, 115.33.55.0/24, 특정 UDP 포트(예: 9090), 특정 TCP 포트(예: 8080) 허용으로 설정하면 된다.

 

아래 화면을 참고~~~

 

 

 

이렇게 Security Rule을 추가하고, 다시 Public IP와 UDP 포트를 이용하여 트래픽을 보내보면, Pod 내부까지 UDP 트래픽이 잘 전달되는 것을 확인할 수 있다.

 

 


조심해야 할 사항:
  Security Rule 설정하고, 40초 후에 Traffic Test해야 한다.
  Node Security Rule이 적용되기 까지 40초가 걸리기 때문에 그 이후에 Traffic Test를 해야 제대로 Traffic이 Pod까지 전달된다.
  

 


생각해볼 내용:
  Node Security List에 TCP Rule을 추가할 때, 왜 Egress Rule도 추가해야 하는지 잘 이해가 안 된다.
  Cluster 외부에서 Ingress Rule에 따라 TCP Traffic을 허용해주면, ConnTrack DB에 Pin Hole 같은게 생기면서
  TCP의 응답 패킷도 알아서 Pass(Forward)시켜주는게 일반적인 Traffic 처리가 아닐까라고 생각했는데... 이게 아닌가 보다.
  아무튼 귀찮지만 Ingress에 추가한 Rule을 Egress에도 꼭 추가해야 한다. (잊지 말자 !!!)

 

 

 

 

참고하면 좋은 Web Docs

Oracle OCI Network Load Balancer 사용하기

https://thekoguryo.github.io/release-notes/20220315-support-for-oci-network-load-balancers/

 

Support for OCI Network Load Balancers

OKE에서 Service Type을 Load Balancer를 사용할때 이제는 OCI Network Load Balancer을 추가적으로 지원합니다.

thekoguryo.github.io

 

위 문서는 간단하게 사용법 위주로 설명되어 있고, 아래 문서는 내부 동작에 관해 자세하게 내용을 다루고 있다.

특히, Service Manifest의 externalTrafficPolicy: local 방식에 관해 궁금하다면 아래 문서를 보는 것이 좋다.

 

https://blogs.oracle.com/cloud-infrastructure/post/network-load-balancer-support-on-oracle-kubernetes-engine

 

Using a network load balancer for Kubernetes services

Announcing network load balancer support on Oracle Container Engine for Kubernetes.

orasites-prodapp.cec.ocp.oraclecloud.com

 

더 자세히 파고 들어가고 싶다면, Oracle 공식 Web Docs를 보길~~~

 

https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengcreatingloadbalancer.htm

 

Defining Kubernetes Services of Type LoadBalancer

Include in the backend set:

docs.oracle.com

 

반응형
원래는 책과 Kubernetes Web Docs, CNI 문서를 읽고 이해한 것을 정리하려고 했는데,
이미 이렇게 정리한 블로그와 YouTube 영상을 발견해서, 
원래 계획을 접고, 관련 블로그 정보를 리스팅하는 페이지를 만들었다.

 

 

Kubernetes Service Network (YouTube 자료)

이 보다 쉽게 설명할 수 있을까 싶을 정도로 쉽게 설명하는 영상을 찾았다.

 

kubernetes service network

 

 

위 동영상을 본 이후에 아래 Web Docs를 보면, 확실히 Kubernetes의 IP Traffic을 처리하는 방식을 이해하기 좋다.

 

 

Kubernetes Network, Kube-Proxy 전반적인 내용 (Blog 자료)

이 문서는 꼭 보는 것을 권장한다.

 

Kubernetes kube-proxy Mode 분석

 

54. Kubernetes kube-proxy Mode 분석

Kubernetes kube-proxy Mode 분석 [kubernetes kube-proxy 관련 글 목록] 54. Kubernetes kube-proxy Mode 분석 70. Kubernetes kube-proxy IPVS Mode 설정 74. Kubernetes NodePort Networking 분석 (ku..

ikcoo.tistory.com

 

Kubernetes kube-proxy IPVS Mode 설정

 

70. Kubernetes kube-proxy IPVS Mode 설정

Kubernetes kube-proxy IPVS Mode 설정 [kubernetes kube-proxy 관련 글 목록] 54. Kubernetes kube-proxy Mode 분석 70. Kubernetes kube-proxy IPVS Mode 설정 74. Kubernetes NodePort Networking 분..

ikcoo.tistory.com

 

Kubernetes Node Port Networking 분석 (kube-proxy: iptable mode)

 

74. Kubernetes NodePort Networking 분석 (kube-proxy : iptable mode)

kubernetes NodePort Networking 분석 kube-proxy = iptable mode CNI = Flannel [kubernetes kube-proxy 관련 글 목록] 54. Kubernetes kube-proxy Mode 분석 70. Kubernetes kube-proxy IPVS Mode 설정..

ikcoo.tistory.com

 

 

Kubernetes LoadBalancer Networking 분석 (kube-proxy: iptable mode)

 

75. kubernetes LoadBalancer Networking 분석 (kube-proxy : iptable mode)

kubernetes LoadBalancer Networking 분석 kube-proxy = iptable mode CNI = Flannel [kubernetes kube-proxy 관련 글 목록] 54. Kubernetes kube-proxy Mode 분석 70. Kubernetes kube-proxy IPVS Mode ..

ikcoo.tistory.com

 

 

Kubernetes Node Port Networking 분석 (kube-proxy: IPVS mode)

 

76. kubernetes NodePort Networking 분석 (kube-proxy : IPVS mode)

kubernetes NodePort Networking 분석 kube-proxy : IPVS mode CNI = Flannel [kubernetes kube-proxy 관련 글 목록] 54. Kubernetes kube-proxy Mode 분석 70. Kubernetes kube-proxy IPVS Mode 설정 74..

ikcoo.tistory.com

 

 

 

 

IPTABLES :: The Kubernetes Networking Guide

IPTABLES Most of the focus of this section will be on the standard node-local proxy implementation called kube-proxy. It is used by default by most of the Kubernetes orchestrators and is installed as a daemonset on top of an newly bootstrapped cluster: $ k

k8s.networkop.co.uk

 

 

 

 

 

반응형

Kubernetes service의 manifest를 작성하다보면, port와 targetport가 헷갈린다.

각 term이 의미하는 것을 지금 이해해도, 몇 개월 뒤에 다시 service resource에 대한 manifest를 작성하려고 보면 또 헷갈려서 다시 문서를 뒤적거리게 된다.

 

  • Port
    • Service Object 자체의 Port.  즉, 여러 Pod를 묶어서 이 Port 값으로 노출시킨다.
    • 한 kubernetes cluster 내에서 다른 pod가 내 pod에게 Layer 4 메시지를 전송할 때 바라보는 port number.
    • 만약 MetalLB, NGINX 같은 Ingress Gateway를 사용하는 경우라면, 이 'port' 값이 Cluster 외부에서 LB(즉, Ingress Gateway)를 통해 들어오는 Port number가 된다.
      예를 들어,   http://{EXTERNAL-IP}:{SERVICE-PORT}
      이런 형태가 된다.
  • TargetPort
    • 내 pod 안에 있는 container가 listening하고 있는 port number.
    • container(즉, app)이 어떤 port를 listening하고 있는지 정확한 값을 알고 설정해야 한다. (HTTPD 설정시 기본 값을 이용했다면, 대부분 80이지 않을까?)
  • NodePort
    • kubernetes 밖으로 노출시킬 port number.
    • Ingress Gateway 또는 Istio를 사용하는 경우에는 딱히 설정할 필요없는 설정 항목.

 

 

Example

만약 2개의 TCP Port를 Service로 오픈하고 싶다면, 아래와 같이 Service Resource와 Pod Resource를 설정한다.

 

##
## Service Manifest Example
##
kind: Service
metadata:
... 중간 생략 ...
spec:
  ports:
  - name: metrics
    port: 24231
    protocol: TCP
    targetPort: metrics
  - name: logfile-metrics
    port: 2112
    protocol: TCP
    targetPort: logfile-metrics
... 중간 생략 ...   
    


##
## Pod Manifest Example
##
kind: Pod
metadata:
... 중간 생략 ...
spec:
  containers:
    name: my-container  
    ports:
    - containerPort: 24231
      name: metrics
      protocol: TCP
    - containerPort: 2112
      name: logfile-metrics
      protocol: TCP
... 중간 생략 ...

'kubernetes' 카테고리의 다른 글

MetalLB BGP 모드를 이용한 Loadbalaning  (0) 2021.08.30
Istio Circuit Break  (0) 2021.08.07
Dockerfile Example  (0) 2021.07.19
Istio Web Docs - 읽기 좋은 순서대리 정리  (0) 2021.07.19
nsenter  (0) 2021.07.13

+ Recent posts