반응형
작성일: 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-conf.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 서비스 데몬을 재기동
아래 예제 설정과 Python 소스 코드를 이용하면 잘 동작함 ^^
설정 파일 작성 [ 파일명: my-conf.yaml ]
$ cat my-conf.yaml zone_file: /var/cache/bind/my-domain.kr.zone json_file: /var/cache/bind/my-domain.kr.json domain_name: my-domain.kr services: - kube_svc_name: almighty host_name: almighty - kube_svc_name: myhappyserver host_name: myhappynacserver - kube_svc_name: my-ingress-nginx-controller host_name: myproxyserver
BIND9 Zone File 생성을 위한 Jinja2 Template 파일 작성 [ 파일명: zone.j2 ]
$ cat zone.j2 ; ------------------------------------------------------------------------------------------ ; NOTE: ; 이 파일은 대한민국 서울에 사는 Andrew Jeong이 만든 "DNS Auto Configuration Application"이 ; 자동으로 생성한 파일입니다. ; 사용자가 이 파일을 직접 편집해도 일정 시간 이후에 Application에 의해서 재작성될 것입니다. ; 따라서 이 Zone file을 직접 편집하는 것을 금합니다. ; 만약, Zone file에 반영할 내용이 있다면 같은 폴더에 있는 JSON 파일을 수정해야 합니다. ; ------------------------------------------------------------------------------------------ ; Internet Domain Zone : {{ origin }} ; Exported date : {{ now }} $ORIGIN {{ origin }} $TTL {{ ttl }} ; ; SOA Record ; @ IN SOA {{ soa['mname'] }} {{ soa['rname'] }} ( {{ soa['serial'] }} ; serial {{ soa['refresh'] }} ; refresh {{ soa['retry'] }} ; retry {{ soa['expire'] }} ; expire {{ soa['minimum'] }} ; minimum ttl ) {% if ns is defined -%} ; ; NS Records ; {% for entry in ns -%} @ IN NS {{ entry['host'] }} {% endfor %} {% endif %} {%- if mx is defined -%} ; ; MX Records ; {% for entry in mx -%} @ IN MX {{ entry['preference'] }} {{ entry['host'] }} {% endfor %} {% endif %} {%- if spf is defined -%} ; SPF Records {% for entry in spf -%} {{ entry['domain'] }} IN TXT "{{ entry['record'] }}" {% endfor %} {% endif %} {%- if a is defined -%} ; ; A Records ; {% for entry in a -%} {{ "{:<17}".format(entry['name']) }} IN A {{ entry['ip'] }} {% endfor %} {% endif %} {%- if aaaa is defined -%} ; AAAA Records {%- for entry in aaaa -%} {{ entry['name'] }} IN AAAA {{ entry['ip'] }} {% endfor %} {% endif %} {%- if cname is defined -%} ; ; CNAME Records ; {% for entry in cname -%} {{ "{:<17}".format(entry['name']) }} IN CNAME {{ entry['alias'] }} {% endfor %} {% endif %} {%- if ptr is defined -%} ; ; PTR Records ; {% for entry in ptr -%} {{ entry['name'] }} IN PTR "{{ entry['host'] }}" {% endfor %} {% endif %} {%- if txt is defined -%} ; ; TXT Records ; {% for entry in txt -%} {{ entry['name'] }} IN TXT "{{ entry['txt'] }}" {% endfor %} {% endif %} {%- if srv is defined -%} ; ; SRV Records ; {% for entry in srv -%} {{ entry['name'] }} IN SRV {{ entry['priority'] }} {{ entry['weight'] }} {{ entry['port'] }} {{ entry['target'] }} {% endfor %} {%- endif %} ; End of Zone
BIND9 Zone File 생성을 위한 JSON 파일 작성 [ 파일명: /var/cache/bind/my-domain.kr.json ]
$ cat /var/cache/bind/my-domain.kr.json { "origin": "my-domain.kr.", "ttl": 600, "soa": { "mname": "ns.my-domain.kr.", "rname": "sejong.my-domain.kr.", "serial": "2024041500", "refresh": 3600, "retry": 600, "expire": 604800, "minimum": 86400 }, "ns": [ { "host": "ns.my-domain.kr." }, { "host": "ns1.my-domain.kr." } ], "a": [ { "name": "@", "ip": "16.6.80.3" }, { "name": "ns", "ip": "16.6.80.3" }, { "name": "ns1", "ip": "13.2.88.15" }, { "name": "myhappyserver", "ip": "13.93.28.9" }, { "name": "my-ingress-nginx-controller", "ip": "10.1.7.6" }, ... 중간 생략 ... { "name": "almighty", "ip": "129.154.195.186" } ], "cname": [ { "name": "my-nicname-server", "alias": "almighty" }, { "name": "toy", "alias": "my-ingress-nginx-controller" }, ... 중간 생략 ... { "name": "my-supersvc", "alias": "mail.sogang.ac.kr" } ] }
로그 파일 생성을 위한 Python Script 작성 [ 파일명: my_log.py ]
$ cat my_log.py #!/usr/bin/python3 import logging import logging.handlers import os import datetime import threading import time class MyLog(): def __init__(self, dir, logname) -> None: self.logname = logname self.dir = os.path.join(dir,logname) self.InitLogger() def InitLogger(self): ## Log format 설정하기 formatter = logging.Formatter("[%(asctime)s] %(levelname)s %(filename)s:%(lineno)d - %(message)s") if os.path.exists(self.dir) == False : os.makedirs(self.dir) log_File = os.path.join(self.dir, self.logname + ".log") timedfilehandler = logging.handlers.TimedRotatingFileHandler(filename=log_File, when='midnight', interval=1, encoding='utf-8') timedfilehandler.setFormatter(formatter) timedfilehandler.suffix = "%Y%m%d.log" self.logger = logging.getLogger(self.logname) self.logger.addHandler(timedfilehandler) self.logger.setLevel(logging.INFO) self.delete_old_log(self.dir, 3) now = datetime.datetime.now() self.toDay = "%04d-%02d-%02d" % (now.year, now.month, now.day) self.th_auto_delete = threading.Thread(target=self.auto_delete, daemon=True) self.th_auto_delete.start() ''' 함수 인자 설명: - fpath:삭제할 파일이 있는 디렉토리, - age:경과일수 ''' def delete_old_log(self, fpath, age): for fp in os.listdir(fpath): fp = os.path.join(fpath, fp) if os.path.isfile(fp): timestamp_now = datetime.datetime.now().timestamp() if os.stat(fp).st_mtime < timestamp_now - (age * 24 * 60 * 60): try: os.remove(fp) except OSError: print(fp, 'this log file is not deleted') def auto_delete(self): while True: now = datetime.datetime.now() day = "%04d-%02d-%02d" % (now.year, now.month, now.day) if self.toDay != day: self.toDay = day self.delete_old_log(self.dir, 3) time.sleep(600) ## This is test code. if __name__ == '__main__': log = MyLog("my_log_test_dir", logname="sejong") for idx in range(3): log.logger.info(f"로그 메시지가 잘 기록되는지 테스트 중 {idx}")
Kubernetes External-IP를 조회 및 DNS 자동 설정을 위한 Python Script 작성 [ 파일명: kube-dns-auto-config.py ]
#!/usr/bin/python3 import os import time import socket import shutil import subprocess import yaml import json import datetime #from datetime import datetime from kubernetes import client, config from jinja2 import Environment, FileSystemLoader import my_log ############################################################### ## NOTE: You have to fix the following variable 'my_run_dir' ############################################################### my_run_dir = "/opt/kube-dns-auto-config/" def search_host_from_json_data(host_name): for item in json_data['a']: if item['name'] == host_name: return item return None def chk_ip_addr_changed(conf_svc_item): kube_svc_name = conf_svc_item["kube_svc_name"] host_name = conf_svc_item["host_name"] log.logger.info(f"\n") log.logger.info(f"[ Checking configuration '{kube_svc_name}' / '{host_name}' ]") for item in kube_svc_info.items: if item.metadata.name == kube_svc_name: ip_addr_k8s_svc = item.status.load_balancer.ingress[0].ip log.logger.info(f" {kube_svc_name:<11} from K8S SVC EXT-IP : {ip_addr_k8s_svc:>15}") dns_record = search_host_from_json_data(host_name) if dns_record == None: log.logger.info(" host_name {host_name} is not found.") return 0 log.logger.info(f" {host_name:<11} from JSON file : {dns_record['ip']:>15}") if ip_addr_k8s_svc == dns_record['ip']: log.logger.info(" IP address of domain name is not changed. Nothing to do.") return 0 else: log.logger.info(f" IP address of domain name is changed; {dns_record['ip']} -> {ip_addr_k8s_svc}") dns_record['ip'] = ip_addr_k8s_svc return 1 return 0 def get_new_serial(old_serial): curr_date = datetime.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" log.logger.info(f"old_serial: [{old_serial}]") log.logger.info(f"new_serial: [{new_serial}]") return new_serial def make_bkup_file(serial): zone_file = my_conf_info['zone_file'] bkup_zone_file = f"{zone_file}_{serial}" shutil.copy(zone_file, bkup_zone_file) bkup_json_file = f"{json_file}_{serial}" shutil.copy(json_file, bkup_json_file) return None def make_zone_file(): ## Jinja2 environment object and refer to templates directory env = Environment(loader=FileSystemLoader(my_run_dir)) template = env.get_template('zone.j2') template.globals['now'] = datetime.datetime.now().isoformat() rendered_data = template.render(json_data) log.logger.info(f"rendered_data: \n{rendered_data}") fp = open(my_conf_info['zone_file'], "w") fp.write(rendered_data) fp.close() return None ## ------------------------------------------------------------------------ ## Begin of Main ## ------------------------------------------------------------------------ if __name__ == '__main__': log = my_log.MyLog("log_kube_dns", logname="kb_log") log.logger.info("##### Start program #####") ## Load configration from YAML file. conf_path = my_run_dir + "my-conf.yaml" with open(conf_path) as fp: my_conf_info = yaml.load(fp, Loader = yaml.FullLoader) json_file = my_conf_info['json_file'] ## Configs can be set in Configuration class directly or using helper utility config.load_kube_config() v1 = client.CoreV1Api() ## ## !!! Main service logic !!! ## while True: svc_ip_changed = 0 with open(json_file) as json_fp: json_data = json.load(json_fp) kube_svc_info = v1.list_service_for_all_namespaces(watch=False) for conf_svc_item in my_conf_info['services']: svc_ip_changed += chk_ip_addr_changed(conf_svc_item) if svc_ip_changed > 0: log.logger.info(f"IP address is changed") log.logger.info(f"json_data: \n\n{json_data}\n") new_serial = get_new_serial(json_data['soa']['serial']) json_data['soa']['serial'] = new_serial ## BKUP file 만들기 make_bkup_file(new_serial) ## JSON 파일에 변경된 내용을 Update한다. json_str = json.dumps(json_data, indent=4) with open(json_file, "w") as json_outfile: json_outfile.write(json_str) ## JSON Data를 Zone DB 포맷으로 변환하여 Zone file에 저장 make_zone_file() ## Restart BIND9 service ret_value = subprocess.call("systemctl restart bind9", shell=True) log.logger.info(f"\n\n") log.logger.info(f"[ BIND9 is restarted ] 'systemctl' return: {ret_value}\n\n") time.sleep(60) ## ------------------------------------------------------------------------ ## End of Main ## ------------------------------------------------------------------------
실행하기 (테스트하기)
$ /opt/kube-dns-auto-config/kube-dns-auto-config.py
위와 같이 Python script를 실행하고, 로그 파일을 열람한다.
$ tail -f /opt/kube-dns-auto-config/log_kube_dns/kb_log/kb_log.log
주의 사항
위 Python script를 실행하는 DNS 서버 장비에 $KUBE_CONFIG 파일이 존재해야 한다.
예를 들어, /home/sejong/.kube/config 같은 파일이 있어야 이 Python 프로그램이 Kubernetes API server에 접근이 가능하다.
## ## 채용 관련 글 ## 제가 일하고 있는 기업 부설연구소에서 저와 같이 연구/개발할 동료를 찾고 있습니다. (이곳은 개인 블로그라서 기업 이름은 기재하지 않겠습니다. E-mail로 문의주시면 자세한 정보를 공유하겠습니다.) 근무지 위치: 서울시 서초구 서초동, 3호선 남부터미널역 근처 (전철역 출구에서 회사 입구까지 도보로 328m) 필요한 지식 (아래 내용 중에서 70% 정도를 미리 알고 있다면 빠르게 협업할 수 있음): - 운영체제 (학부 3~4학년 때, 컴퓨터공학 운영체제 과목에서 배운 지식 수준): 예를 들어, Processor, Process 생성(Fork)/종료, Memory, 동시성, 병렬처리, OS kernel driver - Linux OS에서 IPC 구현이 가능 예를 들어, MSGQ, SHM, Named PIPE 등 활용하여 Process간 Comm.하는 기능 구현이 가능하면 됨. - Algorithm(C언어, C++ 언어로 구현 가능해야 함) 예를 들어, Hashtable, B-Tree, Qsort 정도를 C 또는 C++로 구현할 수 있을 정도 - Network 패킷 처리 지식(Layer 2 ~ 4, Layer 7) 예를 들어, DHCP Server/Client의 주요 Feature를 구현할 정도의 능력이 있으면 됨. - Netfilter, eBPF 등 (IP packet hooking, ethernet packet 처리, UDP/TCP packet 처리) - IETF RFC 문서를 잘 읽고 이해하는 능력 ^^ # 위에 열거한 내용 외에도 제가 여기 블로그에 적은 내용들이 대부분 업무하면서 관련이 있는 주제를 기록한 것이라서 # 이 블로그에 있는 내용들을 잘 알고 있다면, 저희 연구소에 와서 연구/개발 업무를 수행함에 있어서 어려움이 없을 겁니다. 회사에서 사용하는 프로그래밍 언어: - 프로그래밍 언어: C, C++, Go (참고: 아직 연구소 동료들이 Rust를 사용하진 않습니다만, 새 언어로써 Rust를 사용하는 것을 고려하는 중) 근무 시간: - 출근: 8~10시 사이에서 자유롭게 선택 - 퇴근: 8시간 근무 후 퇴근 (오후 5시 ~ 7시 사이) - 야근 여부: 거의 없음 (내 경우, 올해 상반기 6개월간 7시 이후에 퇴근한 경우가 2회 있었음) - 회식 여부: 자유 (1년에 2회 정도 회식하는데, 본인이 집에 가고 싶으면 회식에 안 감. 왜 참석 안 하는지 묻지도 않음) 외근 여부: - 신규 프로젝트 멤버 -> 외근 전혀 하지 않음 (나는 신규 프로젝트만 참여해서 지난 1년 동안 한번도 외근 없었음) - 상용 프로젝트 멤버 -> 1년에 5회 미만 정도로 외근 팀 워크샵 여부: - 팀 워크샵 자체를 진행하지 않음. (워크샵 참석하는 거 싫어하는 개발자 환영 ^^) 연락처: - "sejong.jeonjo@gmail.com" # 궁금한 점은 이 연락처로 문의주세요. - 블로그 비밀 댓글 (제가 하루에 한번씩 댓글 확인하고 있음) 원하는 인재상: - 우리 부설연구소는 "긴 호흡으로 프로젝트를 진행"하기 때문에 최소 2년간 한 가지 주제를 꾸준하게 연구/개발할 수 있는 개발자를 원함. - 우리 부설연구소는 자주적으로 연구 주제를 찾아서 업무를 하기 때문에 능동적으로 생각하고 행동하는 동료를 원함. - 차분하게 연구 주제에 몰입하고, 해법을 찾는 것을 즐기는 사람. 내가 느끼는 우리 연구소의 장점: - 갑/을 관계가 없음. (제가 근무하고 있는 연구소는 SI업종이 아니라서 갑/을 회사 개념이 없음) - 연구소 자체적으로 연구 주제를 발굴하고 시스템을 개발하기 때문에 개발 일정에 대한 스트레스가 적음 - 빌딩 전체를 우리 회사가 사용하므로 분위기가 산만하지 않음. - 근처에 예술의전당, 우면산 둘레길이 있어서 점심 시간에 산책하기 좋음 ^^ - 연구소 동료들 매너가 Good (2년간 일하면서 한번도 감정에 스크레치 생기거나 얼굴 붉히며 싸운 적 없음 ^^)
'kubernetes' 카테고리의 다른 글
Kubernetes에 ceph cluster 구축하기 (rook를 이용하여 ceph 설치) (1) | 2024.08.07 |
---|---|
Kubernetes Service 리소스의 External-IP를 DNS에 자동 설정하는 Python Script [ Ver 1.0 ] (0) | 2024.03.25 |
Oracle OKE Kubernetes worker node에 SSH 접속하는 방법 (2) | 2023.11.21 |
Kubernetes cluster에 Prometheus와 Grafana 설치 (Helm 챠트 이용) (0) | 2023.10.26 |
Kubernetes 스터디 자료 모음 (0) | 2023.08.24 |