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 파일을 작성할 때마다
- Reserved Public IP 생성 요청
- 위에서 생성된 Public IP 값을 확인 후 Service Manifest 작성(또는 Helm chart) 작성
한다는 것은 참 불편한 작업이다.
그래서 아래의 기능을 수행하는 Python script를 만들었다.
- my.yaml 파일에서 Public IP address 정보를 갱신할 'Kubernetes service' 정보와 'Internet domain name' 목록을 설정
- DNS 서버에서 직접 Kubernetes API server에게 매번 변경되는 Service resource의 public IP address를 조회 요청
- 이렇게 얻은 public IP address(즉, EXTERNAL-IP)를 자동으로 DNS 서버의 Zone file에 반영
- 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
... 내용 생략 ...
'kubernetes' 카테고리의 다른 글
Kubernetes Service 리소스의 External-IP를 DNS에 자동 설정하는 Python Script [ Ver 2.0 ] (0) | 2024.08.07 |
---|---|
Kubernetes에 ceph cluster 구축하기 (rook를 이용하여 ceph 설치) (1) | 2024.08.07 |
Oracle OKE Kubernetes worker node에 SSH 접속하는 방법 (2) | 2023.11.21 |
Kubernetes cluster에 Prometheus와 Grafana 설치 (Helm 챠트 이용) (0) | 2023.10.26 |
Kubernetes 스터디 자료 모음 (0) | 2023.08.24 |