반응형

 

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
... 내용 생략 ...

 

 

 


 

+ Recent posts