반응형
작성일: 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-conf.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 서비스 데몬을 재기동

 

아래 예제 설정과 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에 접근이 가능하다.

 

 

 


 

반응형

 

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

 

 

 


 

반응형

 


작성일: 2024년 2월 15일

 

$ kubectl get node -o wide
NAME          STATUS   ROLES   AGE    VERSION   INTERNAL-IP   EXTERNAL-IP       ... 생략 ...
10.0.10.111   Ready    node    11d    v1.25.4   10.0.10.111   130.162.111.222   ...
10.0.10.112   Ready    node    11d    v1.25.4   10.0.10.112   146.156.111.110   ...


##
## 위 worker node 정보 중에서 "EXTERNAL-IP"를 이용하여 SSH 접속
## (Oracle OS를 사용했다면, opc가 기존 계정이다)
##

$ ssh opc@130.162.111.222
(암호 없이 private key를 통해 인증 완료)

##
## 주의: OKE Cluster 구축할 때 사용한 node의 private key, public key 쌍 중에서 private key를
##      내 Client PC에 가지고 있어야 위와 같이 접속된다.
##      그러므로, 꼭 private key 관리를 잘 해야 한다.
##

 


 

반응형

작성일: 2023년 10월 25일

 

내가 참고했던 문서:  https://semaphoreci.com/blog/prometheus-grafana-kubernetes-helm

 

Helm chart를 사용하면, 키보드로 명령을 몇 줄 입력하면 모든 설치 및 구성이 끝난다.

내가 수행했던 명령을 캡쳐했다. 아래 예제를 따라하면 잘 설치된다.

 

/home/sejong/chart/prometheus# helm install -n almighty prometheus ./

NAME: prometheus
LAST DEPLOYED: Wed Oct 25 18:01:27 2023
NAMESPACE: almighty
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-server.almighty.svc.cluster.local


Get the Prometheus server URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace almighty -l "app.kubernetes.io/name=prometheus,app.kubernetes.io/instance=prometheus" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace almighty port-forward $POD_NAME 9090


The Prometheus alertmanager can be accessed via port 9093 on the following DNS name from within your cluster:
prometheus-alertmanager.almighty.svc.cluster.local


Get the Alertmanager URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace almighty -l "app.kubernetes.io/name=alertmanager,app.kubernetes.io/instance=prometheus" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace almighty port-forward $POD_NAME 9093
#################################################################################
######   WARNING: Pod Security Policy has been disabled by default since    #####
######            it deprecated after k8s 1.25+. use                        #####
######            (index .Values "prometheus-node-exporter" "rbac"          #####
###### .          "pspEnabled") with (index .Values                         #####
######            "prometheus-node-exporter" "rbac" "pspAnnotations")       #####
######            in case you still need it.                                #####
#################################################################################


The Prometheus PushGateway can be accessed via port 9091 on the following DNS name from within your cluster:
prometheus-prometheus-pushgateway.almighty.svc.cluster.local


Get the PushGateway URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace almighty -l "app=prometheus-pushgateway,component=pushgateway" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace almighty port-forward $POD_NAME 9091

For more information on running Prometheus, visit:
https://prometheus.io/

/home/sejong/WorkSpace/chart/prometheus#


---



/home/sejong/chart/grafana# helm install -n almighty grafana ./

NAME: grafana
LAST DEPLOYED: Wed Oct 25 18:37:27 2023
NAMESPACE: almighty
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:

   kubectl get secret --namespace almighty grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo


2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:

   grafana.almighty.svc.cluster.local

   Get the Grafana URL to visit by running these commands in the same shell:
     export POD_NAME=$(kubectl get pods --namespace almighty -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}")
     kubectl --namespace almighty port-forward $POD_NAME 3000

3. Login with the password from step 1 and the username: admin

/home/sejong/WorkSpace/chart/grafana#


---


## Grafana admin 계정 암호 찾기

$  kubectl get secret --namespace almighty grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
gxq5MbMBa2w5qkqpvkaYGU5T8bJDOTag9ayy5kxi

 

 

Helm chart가 잘 만들어져 있어서 큰 노력없이 클러스터 구축이 끝났다.

Helm chart를 만든 분께 감사하다는 말을 전하고 싶다.

반응형

 

https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863

 

CloudNet@ Blog

CloudNet@ 팀에서 Cloud Infra & Network 기술에 대한 정보를 공유하는 블로그 입니다.

gasidaseo.notion.site

 

 

이정훈(Jerry)

https://jerryljh.tistory.com/

 

매일 쓰고 달립니다.

문의는 erdia22@gmail.com으로 부탁드립니다.

jerryljh.tistory.com

 

위 블로그 운영자(이정훈 님)이 작성한 Example Code.

https://github.com/wikibook/kubepractice

 

GitHub - wikibook/kubepractice: 《24단계 실습으로 정복하는 쿠버네티스》 예제 코드

《24단계 실습으로 정복하는 쿠버네티스》 예제 코드. Contribute to wikibook/kubepractice development by creating an account on GitHub.

github.com

 

반응형

 

Kubernetes에서 여러 StorageClass를 사용하다보면, 주로 사용하는 1개의 StorageClass가 발생하게 된다. (내 경험상~)

이럴 때, 그 자주 사용하는 StorageClass가 Default로 선택되도록하면 PV 생성할 때 편한데

아래와 같이 하면 된다.

$ kubectl get sc

NAME                        PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
... 중간 생략 ...
rook-ceph-block             rook-ceph.rbd.csi.ceph.com     Retain          Immediate              true                   203d
... 중간 생략 ...


##
## 위 StorageClass 리스트 중에서 rook-ceph-block을 default로 만들기.
##

$ kubectl patch storageclass rook-ceph-block -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

storageclass.storage.k8s.io/rook-ceph-block patched

$ kubectl get sc rook-ceph-block -o yaml

... 중간 생략 ...
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-ceph-block
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
... 중간 생략 ...  


##
## StorageClass 리스트를 조회해보면, 
## 아래와 같이 'rook-ceph-block' 이름 옆에 (default)로 표시되어 있다.
##

$ kubectl get sc

NAME                        PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
... 중간 생략 ...
rook-ceph-block (default)   rook-ceph.rbd.csi.ceph.com     Retain          Immediate              true                   203d
... 중간 생략 ...

 

 

자세한 설명을 보고 싶다면 아래 Web Docs를 참고.

https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/  

 


 

반응형

 

작성일: 2024년 3월 11일

 

여담 1:
이전에는 kubeadm 명령 도구를 이용해서 kubernetes cluster를 구축했었는데,
오늘은 만사가 귀찮아서 kubespray를 이용해서 kubernetes cluster를 구축해보기로 했다.
kubespray를 이용하면 containerd, CNI, MetalLB 같은 패키지를 직접 설치하지 않아도 되니까 시간이 많이 절약된다.
Ubuntu 22.04 OS가 설치되어 있다고 가정하면, 대략 아래 절차를 따라했을 때 30분 정도 시간이 소요되는 것 같다.

 

여담 2:
아래 작업을 3가지 CPU 스펙으로 진행해봤는데, 저사양 CPU에서는 kubespray를 수행하는 중간에 실패한다.
특히 kubespray를 실행하는 노드(예를 들어 master-a 노드)의 CPU 스펙이 중요하다.
내가 테스트했던 CPU 스펙과 성공/실패 여부를 정리해보면;
CPU 스펙 Kubespray 동작 결과
Intel(R) Xeon(R) E-2278G CPU @ 3.40GHz (16 쓰레드) K8S 클러스터 구성 성공
Intel(R) Xeon(R) E-2124G CPU @ 3.40GHz (4 쓰레드) K8S 클러스터 구성 실패
Intel(R) Core(TM) i7-11700 @ 2.50GHz (16 쓰레드) K8S 클러스터 구성 성공

 

.

.

.

 

Kubernetes cluster 구축 환경 및 Network 구성 정보  [ K8S 클러스터 설계 ]

주의:
아래 예시는 kubespray를 실행할 장비를 별도로 만들지 않았는데, 금전적으로 여유가 있거나 이미 HW가 넉넉히 있는 경우라면
kubespray 실행용 장비를 따로 가지고 있으면 좋다.
Master node: 3개 (Ubuntu 22.04)
  - CPU: 2 core
  - MEM: 8GB
  - OS: Ubuntu 22.04

Worker node: 3개 (Ubuntu 22.04)
  - CPU: 4 core
  - MEM: 16GB
  - OS: Ubuntu 22.04

##
## 참고: CPU, MEM가 적어서 master-a 장비에서 kubespray를 실행하겠다.  
##      만약, HW 제원이 넉넉하다면 kubespray를 실행할 전용 장비가 있으면 좋다.  
##

Network 구성:
  - master-a: 10.10.1.11
  - master-b: 10.10.1.12
  - master-c: 10.10.1.13
  - worker-a: 10.10.1.101
  - worker-b: 10.10.1.102
  - worker-c: 10.10.1.103

 

 

OS & 접속 계정 설정

Master node 및 Worker node로 사용할 6개의 Ubuntu 22.04 서버를 설치한다.

아래 2가지 중에서 편한 방법으로 구성하면 된다.
  - 물리 장비에 OS를 설치하거나 VM에 OS를 설치. (실제로 상용 서비스를 할 것이라면, 이 방법을 권장)
  - 단순히 학습을 목적으로 Kubernetes cluster를 설치한다면, VM에 OS를 설치하는 것을 권장.

 

[ 작업 대상 장비: master-a 노드 ]
'master-a' 에서 아래와 같이 Private key, Public key를 생성하고
이렇게 생성한 Public key를 모든 kubernetes node에 복사한다.

## 이 명령은 master-a 노드에서 수행해야 한다.

$ ssh-keygen 

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
... 생략 ...

$ ssh-copy-id  sejong@master-a
...

$ ssh-copy-id  sejong@master-b
...

$ ssh-copy-id  sejong@master-c
...

$ ssh-copy-id  sejong@worker-a
...

$ ssh-copy-id  sejong@worker-b
...

$ ssh-copy-id  sejong@worker-c
...

 

 

[ 작업 대상 장비: 모든 노드 ]

Password 없이 Root 권한을 얻기 위해 각 노드에서 아래와 같이 작업을 수행한다. (Ansible이 동작하기 위해 필요한 작업)
$ sudo -
$ echo "sejong ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

 

 

[ 작업 대상 장비: 모든 노드 ]

각 노드의 Firewall 기능이 disable되어 있는지 확인한다.
$ ufw status
Status: inactive
 
 

[ 작업 대상 장비: 모든 노드 ]

각 노드의 /etc/hosts 파일에 아래의 내용을 추가한다.
$ cat /etc/hosts

... 중간 생략 ...

10.10.1.11 master-a
10.10.1.12 master-b
10.10.1.13 master-c

10.10.1.101 worker-a
10.10.1.102 worker-b
10.10.1.103 worker-c

... 중간 생략 ...
 

Kubespray 실행을 위한 준비 작업

[ 작업 대상 장비: master-a 노드 ]

$ sudo -s

$ apt update

$ apt install python3-pip

$ git clone https://github.com/kubernetes-sigs/kubespray.git

$ cd kubespray

$ pip3 install -r requirements.txt

$ cd inventory

$ cp -Rp sample/ mycluster


$ cat hosts.yaml

all:
  hosts:
    master-a:
      ansible_host: master-a
    master-b:
      ansible_host: master-b
    master-c:
      ansible_host: master-c
    worker-a:
      ansible_host: worker-a
    worker-b:
      ansible_host: worker-b
    worker-c:
      ansible_host: worker-c      
  children:
    kube_control_plane:
      hosts:
        master-a:
        master-b:
        master-c:
    kube_node:
      hosts:
        worker-a:
        worker-b:
        worker-c:
    etcd:
      hosts:
        master-a:
        master-b:
        master-c:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
        calico_rr:
    calico_rr:
      hosts: {}


$ cat  ./group_vars/k8s_cluster/k8s-cluster.yml

... 중간 생략 ...

## Kube cluster를 구축하고 나서, MetalLB를 설치할 때 strict_arp 설정이 필요하다.
## 따라서 MetalLB를 설치하려는 경우라면, 아래 설정을 꼭 'true'로 변경해야 한다.
kube_proxy_strict_arp: true
... 중간 생략 ...
container_manager: containerd
... 중간 생략 ...
kubernetes_audit: true
... 중간 생략 ...

$

 

 

Kubespray 실행하기 (Kubernetes Cluster 생성하기)

[ 작업 대상 장비: master-a 노드 ]

##
## kubespray 소스 폴더의 최상위 폴더로 이동 후 아래 명령을 실행
## (참고) root 계정, 일반 계정 어떤 것으로 ansible-playbook 명령을 실행해도 괜찮다.
##       단, 위에서 public key를 모든 노드에 복사(ssh-copy-id)할 때 어떤 계정의 public key를
##       사용했느냐에 따라 동일한 계정으로 아래 명령을 수행하면 된다.
##

$ ansible-playbook -v -i inventory/mycluster/hosts.yaml --become --become-user=root cluster.yml

... 중간 생략 ...

TASK [network_plugin/calico : Set calico_pool_conf] *****************************************************************************************************************************************
ok: [master-a] => {"ansible_facts": {"calico_pool_conf": {"apiVersion": "projectcalico.org/v3", "kind": "IPPool", "metadata": {"creationTimestamp": "2024-03-08T15:06:52Z", "name": "default-pool", "resourceVersion": "4122", "uid": "ffb5f2e0-85f1-4f3b-9c9d-55fe86be8c97"}, "spec": {"allowedUses": ["Workload", "Tunnel"], "blockSize": 26, "cidr": "10.233.64.0/18", "ipipMode": "Never", "natOutgoing": true, "nodeSelector": "all()", "vxlanMode": "Always"}}}, "changed": false}
Friday 08 March 2024  15:29:07 +0000 (0:00:00.054)       0:19:02.007 **********

TASK [network_plugin/calico : Check if inventory match current cluster configuration] *******************************************************************************************************
ok: [master-a] => {
    "changed": false,
    "msg": "All assertions passed"
}
Friday 08 March 2024  15:29:07 +0000 (0:00:00.060)       0:19:02.067 **********
Friday 08 March 2024  15:29:07 +0000 (0:00:00.037)       0:19:02.104 **********
Friday 08 March 2024  15:29:07 +0000 (0:00:00.045)       0:19:02.150 **********

PLAY RECAP **********************************************************************************************************************************************************************************
master-a                   : ok=676  changed=35   unreachable=0    failed=0    skipped=1164 rescued=0    ignored=2
master-b                   : ok=580  changed=25   unreachable=0    failed=0    skipped=1046 rescued=0    ignored=1
master-c                   : ok=582  changed=25   unreachable=0    failed=0    skipped=1044 rescued=0    ignored=1
worker-a                   : ok=419  changed=8    unreachable=0    failed=0    skipped=695  rescued=0    ignored=1
worker-b                   : ok=419  changed=8    unreachable=0    failed=0    skipped=691  rescued=0    ignored=1
worker-c                   : ok=419  changed=8    unreachable=0    failed=0    skipped=691  rescued=0    ignored=1

Friday 08 March 2024  15:29:07 +0000 (0:00:00.309)       0:19:02.459 **********
===============================================================================
kubernetes/kubeadm : Restart all kube-proxy pods to ensure that they load the new configmap ----------------------------------------------------------------------------------------- 71.14s
network_plugin/calico : Check if calico ready --------------------------------------------------------------------------------------------------------------------------------------- 69.41s
kubernetes-apps/ansible : Kubernetes Apps | Start Resources ------------------------------------------------------------------------------------------------------------------------- 65.75s
network_plugin/calico : Calico | Create Calico Kubernetes datastore resources ------------------------------------------------------------------------------------------------------- 41.63s
kubernetes/control-plane : Upload certificates so they are fresh and not expired ---------------------------------------------------------------------------------------------------- 35.07s
etcd : Configure | Wait for etcd cluster to be healthy ------------------------------------------------------------------------------------------------------------------------------ 29.78s
kubernetes/preinstall : Preinstall | restart kube-apiserver crio/containerd --------------------------------------------------------------------------------------------------------- 27.93s
policy_controller/calico : Start of Calico kube controllers ------------------------------------------------------------------------------------------------------------------------- 26.35s
etcd : Reload etcd ------------------------------------------------------------------------------------------------------------------------------------------------------------------ 21.45s
kubernetes/preinstall : Preinstall | restart kube-controller-manager crio/containerd ------------------------------------------------------------------------------------------------ 20.39s
network_plugin/calico : Calico | Configure calico network pool ---------------------------------------------------------------------------------------------------------------------- 15.41s
network_plugin/calico : Start Calico resources -------------------------------------------------------------------------------------------------------------------------------------- 14.03s
kubernetes/control-plane : Create kubeadm token for joining nodes with 24h expiration (default) ------------------------------------------------------------------------------------- 13.13s
etcd : Wait for etcd up ------------------------------------------------------------------------------------------------------------------------------------------------------------- 12.82s
container-engine/runc : Download_file | Download item ------------------------------------------------------------------------------------------------------------------------------- 12.65s
container-engine/crictl : Download_file | Download item ----------------------------------------------------------------------------------------------------------------------------- 12.60s
container-engine/containerd : Download_file | Download item ------------------------------------------------------------------------------------------------------------------------- 12.17s
kubernetes/control-plane : Check which kube-control nodes are already members of the cluster ---------------------------------------------------------------------------------------- 11.68s
container-engine/nerdctl : Download_file | Download item ---------------------------------------------------------------------------------------------------------------------------- 11.57s
etcd : Backup etcd v2 data ---------------------------------------------------------------------------------------------------------------------------------------------------------- 11.29s

$

 

[ 참고 ]
가끔 위 ansible-playbook 명령이 실패하는 경우가 있다.
그럴 때는 동일하게 한번 더 실행하면 해결되는 경우가 많다. (특히, CPU와 Memory 성능이 낮은 경우)

 

위 Ansible playbook이 정상적으로 수행되었다면,
아래와 같이 root 계정 권한으로 kubectl 명령을 수행해본다.

 

$ sudo -s

$ kubectl get node -o wide

NAME       STATUS   ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
master-a   Ready    control-plane   35m   v1.29.2   172.17.1.11    <none>        Ubuntu 22.04.4 LTS   5.15.0-100-generic   containerd://1.7.13
master-b   Ready    control-plane   34m   v1.29.2   172.17.1.12    <none>        Ubuntu 22.04.4 LTS   5.15.0-100-generic   containerd://1.7.13
master-c   Ready    control-plane   33m   v1.29.2   172.17.1.13    <none>        Ubuntu 22.04.4 LTS   5.15.0-100-generic   containerd://1.7.13
worker-a   Ready    <none>          31m   v1.29.2   172.17.1.101   <none>        Ubuntu 22.04.4 LTS   5.15.0-100-generic   containerd://1.7.13
worker-b   Ready    <none>          31m   v1.29.2   172.17.1.102   <none>        Ubuntu 22.04.4 LTS   5.15.0-100-generic   containerd://1.7.13
worker-c   Ready    <none>          31m   v1.29.2   172.17.1.103   <none>        Ubuntu 22.04.4 LTS   5.15.0-100-generic   containerd://1.7.13


$ kubectl get pod -A

NAMESPACE     NAME                                      READY   STATUS              RESTARTS         AGE
kube-system   calico-kube-controllers-648dffd99-l9pnt   1/1     Running             0                12m
kube-system   calico-node-4q5q9                         1/1     Running             11 (18m ago)     31m
kube-system   calico-node-98pdw                         1/1     Running             14 (5m52s ago)   31m
kube-system   calico-node-nrn9h                         1/1     Running             12 (7m57s ago)   31m
kube-system   calico-node-wgpvw                         1/1     Running             11 (18m ago)     31m
kube-system   coredns-69db55dd76-zkfqn                  1/1     Running             0                10m
kube-system   dns-autoscaler-6f4b597d8c-rt9nk           1/1     Running             0                10m
kube-system   kube-apiserver-master-a                   1/1     Running             4 (18m ago)      37m
kube-system   kube-apiserver-master-b                   1/1     Running             4 (18m ago)      36m
kube-system   kube-apiserver-master-c                   1/1     Running             4 (16m ago)      35m
kube-system   kube-controller-manager-master-a          1/1     Running             8 (6m27s ago)    37m
kube-system   kube-controller-manager-master-c          1/1     Running             10 (7m48s ago)   35m
kube-system   kube-proxy-hgt5x                          1/1     Running             0                15m
kube-system   kube-proxy-j26c8                          1/1     Running             0                15m
kube-system   kube-proxy-ldlb7                          1/1     Running             0                13m
kube-system   kube-proxy-trhgl                          1/1     Running             0                15m
kube-system   kube-proxy-vh6qt                          1/1     Running             0                15m
kube-system   kube-proxy-wv48f                          1/1     Running             0                13m
kube-system   kube-scheduler-master-a                   1/1     Running             7 (10m ago)      37m
kube-system   kube-scheduler-master-b                   1/1     Running             7 (6m15s ago)    36m
kube-system   kube-scheduler-master-c                   1/1     Running             7 (11m ago)      35m
kube-system   nginx-proxy-worker-a                      1/1     Running             0                34m
kube-system   nginx-proxy-worker-b                      1/1     Running             0                34m
kube-system   nginx-proxy-worker-c                      1/1     Running             0                32m
kube-system   nodelocaldns-42f6z                        1/1     Running             0                10m
kube-system   nodelocaldns-5nqjw                        1/1     Running             0                10m
kube-system   nodelocaldns-dsg5w                        1/1     Running             0                10m
kube-system   nodelocaldns-dz9tm                        1/1     Running             0                10m
kube-system   nodelocaldns-mxjxz                        1/1     Running             0                10m
kube-system   nodelocaldns-zqb6h                        1/1     Running             0                10m

$ kubectl get svc -A

NAMESPACE     NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
default       kubernetes   ClusterIP   10.233.0.1   <none>        443/TCP                  37m
kube-system   coredns      ClusterIP   10.233.0.3   <none>        53/UDP,53/TCP,9153/TCP   12m
$

 

 

 

Kubespray ansible playbook은 11분 42초가 소요되었는데,

조금 더 최신 컴퓨터로 ansible playbook을 실행했다면 10분 이내에 모든 cluster 구축이 완료되지 않을까...

 

[ 참고: 이번 테스트에 사용했던 HW 1 사양 ]

$ lshw -short

H/W path           Device           Class          Description
==============================================================
... 중간 생략 ...
/0/3b                               memory         64GiB System Memory
/0/52                               processor      11th Gen Intel(R) Core(TM) i7-11700 @ 2.50GHz
/0/100/1/0                          display        GA102 [GeForce RTX 3080 Ti]
/0/100/6/0         /dev/nvme0       storage        Samsung SSD 980 PRO 1TB
... 중간 생략 ...

 

[ 참고: 이번 테스트에 사용했던 HW 2 사양 ]

$  lshw -short

H/W path          Device     Class          Description
=======================================================
/0/3a                      memory         64GiB System Memory
/0/3a/0                    memory         32GiB SODIMM DDR4 Synchronous 3200 MHz (0.3 ns)
/0/3a/2                    memory         32GiB SODIMM DDR4 Synchronous 3200 MHz (0.3 ns)
/0/48                      processor      Intel(R) Xeon(R) E-2278G CPU @ 3.40GHz
... 중간 생략 ...

 


 

예제 Pod, Service를 배포하기

아래 글에 간단하게 Pod, Service 리소스를 생성하는 예제 코드 및 명령이 있다.

https://andrewpage.tistory.com/68

 

 


 

 

 

Troubleshooting, Q&A

 

Kubernetes cluster 초기화 하기

$ ansible-playbook -v -i inventory/mycluster/hosts.yaml --become --become-user=root reset.yml

 

Kubernetes cluster 를 upgrade 하기

Upgrade 시나리오가 다양하기 때문에 아래 글을 자세히 읽고 Upgrade를 수행하는 것을 권장한다.

https://github.com/kubernetes-sigs/kubespray/blob/master/docs/upgrades.md

 

Kubernetes node를 추가, 대체하기 (Join new node, Replace node)

Kubernetes node에 대한 operation이 다양하기 때문에 아래 글을 자세히 읽고 node 관리하는 것을 권장한다.

https://github.com/kubernetes-sigs/kubespray/blob/master/docs/nodes.md

 

 

 

 

 

블로그 작성자: sejong.jeonjo@gmail.com

 

 

반응형

 

데브시스터즈 Kubernetes 도입 사례

https://www.youtube.com/watch?v=8R4DDEqjc0I 

 

Kubernetes를 어떤 방식으로 활용할까, 도입할까 말까를 고민하고 있다면 위 영상이 도움이 될 것 같다.

 

 

 

Kubernetes 인터넷 강의 / 따배쿠 (따라하면서 배우는 Kubernetes)

기초부터 차근차근 배우고 싶다면, 아래 영상을 처음부터 끈기있게 보면 좋다.

거의 책 한권 읽는 수준이다.

 

https://www.youtube.com/results?search_query=%EB%94%B0%EB%B0%B0%EC%BF%A0 

 

https://www.youtube.com/results?search_query=%EB%94%B0%EB%B0%B0%EC%BF%A0

 

www.youtube.com

 

 

Kubernetes에 관한 전반적인 사용 설명

https://www.youtube.com/@Akbun/videos

 

악분 일상

현재 공부한 것들을 공유하는 채널입니다.

www.youtube.com

 

+ Recent posts