Jinja 파일(*.j2)과 YAML 파일(*.yml *.yaml)을 VIM으로 편집할 때
문서 내용이 적절한 Syntax Color로 보여지지 않는다면, 아래 절차를 따라서 VIM 편집기를 설정해보자.
Pathogen 설치하기
## 관련 폴더를 미리 만든다.
$ mkdir -p ~/.vim/autoload ~/.vim/bundle
## pathogen.vim 파일을 다운로드한다.
$ curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
## ~/.vimrc 파일에 아래의 내용을 추가한다.
$ cat ~/.vimrc
... 중간 생략 ...
execute pathogen#infect()
syntax on
filetype plugin indent on
... 중간 생략 ...
VIM Bundle 추가하기
$ cd ~/.vim/bundle
## Ansible, YAML 관련 번들 파일을 다운로드
$ git clone https://github.com/chase/vim-ansible-yaml.git
## Jinja 관련 번들 파일을 다운로드
$ git clone https://github.com/lepture/vim-jinja.git
위에 내가 실제로 VM instance를 Scale-up한 이후의 비용 증가분을 계산한 것은 클라우드 테넌트 계약 조건과 현재 원/달러 환율 따라 30% 정도 차이가 날 것이다. 특히 클라우드 테넌트 계약시, 3년 이상 장기 계약한 경우 많은 할인율이 적용되서 Scale-up에 대한 증가분이 크지 않을 수 있다. 1년 단위로 단기 계약한 테넌트라면, CPU/Memory를 Scale-up할 때 비용 증가분이 위의 계산 결과보다 클 수 있다.
내 밥벌이가 Python 언어로 개발하는 일이 아니다보니, Python으로 뭔가 만드는 일이 1년에 한 번 있을까 말까한다. (누가 시켜서 만든다기 보다는 일하다가 자동화가 필요한 상황이 생길 때 ~~~)
오늘도 Python Logging 기능을 5년 만에 쓸 일이 있어서 다시 작성하려고 보니, 구글링해야 하고.. 예제 코드 작성해서 확인하고, 그런 후에 작성하려고 한 application에 적용하고... 이렇게 시간을 보내고 나니까 나 스스로 참 답답한 느낌이다. 다음 5년 뒤에도 이런 답답함이 없기를 바라면서 오늘 작성한 것을 잘 메모해야 겠다 ^^
#!/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}")
테스트는 아래와 같이 하면 된다. 일단, 잘 동작함, OK !!
$ ./my_log.py
...
위 'MyLog' 클래스를 다른 Application code에서 참고한다면, 아래 예제와 같이 작성하면 된다.
참고: my_log.py는 아래 소스 코드 my_sample_app.py와 같은 디렉토리에 있어야 한다.
#!/usr/bin/python3
import my_log
...
... 중간 생략 ...
...
if __name__ == '__main__':
log = my_log.MyLog("log_my_sample_dir", logname="my_sample")
log.logger.info("##### Start program #####")
... 중간 생략 ...
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
## ------------------------------------------------------------------------