반응형

 

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

 

 

 


 

반응형

 


 

작성일: 2023년 12월 12일

 

 

Juniper Switch를 원격에서 제어할 수 있는 API를 찾아보니 아래와 같은 것들이 있었다.

- Python PKG (Juniper가 공식적으로 개발 및 배포하고 Example이 다양하게 많아서 사용하기 좋았다.)

- Ansible Module (Juniper가 공시적으로 배포하지만, 실전에서 사용하기에는 Example이 부족했다)

- SNMP (Juniper가 SNMP 서비스를 제공하지만, 제약이 많다)

- NET-CONF (업계 표준이니까 제공해주지만, 쌩코딩해서 사용하기에는 시간적으로 부담이 된다)

 

구현 시간을 아끼고, 예제도 많은 Python PKG를 선택해서 개발하기로 마음을 굳혔다.

그리고 나중에 시간이 생기면 Ansible playbook을 작성해서 조금 더 추상화해볼 계획이다.


 

 

참고:

아래의 예제는 [ Junos PyEX 개발자 가이드 ] 문서 중에서 업무에 필요한 부분만 추려서 실습하고 작성한 것이다.

시간이 넉넉한 사람은 아래 개발자 가이드를 다 읽어보고 Junos PyEX를 사용하고,

시간이 넉넉하지 않은 사람은 아래 요약된 실습 내용만 따라하면 된다.

https://www.juniper.net/documentation/kr/ko/software/junos-pyez/junos-pyez-developer/index.html

 

 

 

준비 작업 / Juniper Switch 장비 설정 작업

Client 장비의 Junos PyEX가 접근할 수 있도록 Juniper Switch에서는 [ SSH 서비스, NETCONF, Junos XML API ]를 활성화(Enable)해야 한다.

설명은 거창하지만, 아래의 예제 명령을 한줄 입력해주면 끝이다.

 

> configure         // 설정 모드로 진입하기 위한 명령을 입력

Entering configuration mode
Users currently editing the configuration:
... 중간 생략 ...

myuser# set netconf ssh   // NETCONF-over-SSH 서비스를 활성화하는 명령을 입력

myuser# commit            // 위에서 변경한 내용을 시스템에 반영하기 위한 명령을 입력

 

 

Junos PyEX 패키지(jnpr.junos)의 각 모듈에 대한 설명

Module Description
device Junos 디바이스를 표현하는 클래스를 정의하고, Device spec 및 상태를 조회할 수 있는 기능을 지원한다.
command CLI, vty 명령을 지원한다. 
표 형태의 View 출력 Form을 지원한다.
exception Exception handling
factory 사용자가 지정한 형태의 Table, View 출력이 가능하도록 한다.
  예: loadyaml() 함수
facts Device에 대한 정보를 추출하기 위한 Object
op RPC 응답의 XML 출력을 Filtering
resources ... 설명 생략 ...
transport ... 설명 생략 ...
utils Configuration utility, Shell utility, Software installation, Security utility, etc ...

 

준비 작업 / Client 장비에 Python 패키지 설치 작업

내가 사용한 OS: Ubuntu 22.04

 

Juniper Switch를 관리할 Client 장비(Ubuntu 22.04)에서 아래 명령을 수행하여 Junos PyEX 패키지를 설치한다.

$ sudo pip3 install junos-eznc

 

작업 끝. (너무 간단하다 ^^)

 

[ 예제 1 ]  네트워크 포트 Up & Down 예제 프로그램

아래 예제는 Juniper Switch(10.1.1.2) 장치에 접속해서 Giga Ethernet을 Down & Up 상태로 변경하고

Port 상태를 조회하는 기능을 수행한다.

 

아래와 같이 Python 예제 코드를 작성한다.

from jnpr.junos import Device
from jnpr.junos.utils.config import Config
from jnpr.junos.op.ethport import EthPortTable
import time


with Device(host='10.1.1.2', user='myadmin', password='mypasswd') as dev:
    ## 포트 상태를 Up/Down 상태로 변경하기
    with Config(dev, mode='ephemeral') as cu:
        cu.load('set interfaces ge-0/0/8 disable', format='set')      # Port Down 상태로 변경
        #cu.load('delete interfaces ge-0/0/8 disable', format='set')  # Port Up 상태로 변경
        cu.commit()

    time.sleep(5)   ## Config를 commit하고도 2~3초 정도 지나야 Port state 값이 반영되므로 5초 정도 sleep 한다.
    
    ## 전체 포트 상태 정보를 가져오기
    eths = EthPortTable(dev)
    eths.get()
    for item in eths:
        print ("{}: {}".format(item.name, item.oper))

    # 1개 포트 정보만 가져오기
    eths.get("ge-0/0/8")
    # 포트 정보의 모든 컬럼의 내용을 보고 싶다면...
    print(eths.values())
    for item in eths:
        print ("name: {}  operating state: {}  admin state: {}  mac: {}  mtu: {}".format(item.name, item.oper, item.admin, item.macaddr, item.mtu))

 

 

위에서 작성한 Python 예제 코드를 아래와 같이 실행한다.

$  python3 my_example_1.py

ge-0/0/0: down
ge-0/0/1: down
ge-0/0/2: down
ge-0/0/3: down
ge-0/0/4: down
ge-0/0/5: down
ge-0/0/6: down
ge-0/0/7: down
ge-0/0/8: up         <-- 내가 변경한 포트의 상태
ge-0/0/9: down
ge-0/0/10: down
ge-0/0/11: down
ge-0/0/12: down
ge-0/0/13: down
ge-0/0/14: down
ge-0/0/15: down
ge-0/0/16: down
ge-0/0/17: down
ge-0/0/18: down
ge-0/0/19: down
ge-0/0/20: down
ge-0/0/21: down
ge-0/0/22: down
ge-0/0/23: up

[[('oper', 'up'), ('admin', 'up'), ('description', None), ('mtu', 1514), ('link_mode', 'Full-duplex'), ('macaddr', '60:c7:8d:a3:78:74'), ('rx_bytes', '2124'), ('rx_packets', '24'), ('tx_bytes', '586755785'), ('tx_packets', '7013363'), ('running', True), ('present', True)]]

name: ge-0/0/8  operating state: up  admin state: up  mac: 60:c3:8a:a3:78:74  mtu: 1514

$

 

 

 


 

[ 예제 2 ]  Juniper Switch의 VLAN 별, 포트 별 Learning된 MAC Address List를 조회

아래 예제는 Juniper Switch(10.1.1.2) 장치에 접속해서 Juniper Switch가 MAC Learning한 결과물을 조회한다.

Learning된 MAC Address를 각각의 VLAN 별, 포트별로 표현된다.

 

조회 결과물이 XML이기 때문에 XML 데이터 포맷을 처리하기 위한 Python 패키지가 필요한다.

$ pip install xmltodict

 

 

아래와 같이 Python 예제 코드를 작성한다.

import pprint
import xmltodict
from xml.etree import ElementTree
from jnpr.junos import Device
from jnpr.junos.op.ethernetswitchingtable import EthernetSwitchingTable


with Device(host='10.1.1.2', user='myadmin', password='mypasswd') as dev:
    eth_table = dev.rpc.get_ethernet_switching_table_information()
    dict_eth_table = xmltodict.parse(ElementTree.tostring(eth_table, method="xml"))
    pprint.pprint(dict_eth_table, indent=2)

 

 

위에서 작성한 Python 예제 코드를 아래와 같이 실행한다.

$  python3  my_example_2.py

{ 'l2ng-l2ald-rtb-macdb':
    { 'l2ng-l2ald-mac-entry-vlan':
        { '@style': 'brief-rtb',
            'l2ng-l2-mac-routing-instance': 'default-switch',
            'l2ng-l2-vlan-id': '100',
            'l2ng-mac-entry': [
                { 'l2ng-l2-mac-address': '00:03:51:17:07:14',
                  'l2ng-l2-mac-age': '-',
                  'l2ng-l2-mac-flags': 'D',
                  'l2ng-l2-mac-fwd-next-hop': '0',
                  'l2ng-l2-mac-logical-interface': 'ge-0/0/23.0',
                  'l2ng-l2-mac-rtr-id': '0',
                  'l2ng-l2-mac-vlan-name': 'vlan-100'},
                { 'l2ng-l2-mac-address': '00:03:51:20:02:e2',
                  'l2ng-l2-mac-age': '-',
                  'l2ng-l2-mac-flags': 'D',
                  'l2ng-l2-mac-fwd-next-hop': '0',
                  'l2ng-l2-mac-logical-interface': 'ge-0/0/23.0',
                  'l2ng-l2-mac-rtr-id': '0',
                  'l2ng-l2-mac-vlan-name': 'vlan-100'},

                ... 중간 생략 ...

                { 'l2ng-l2-mac-address': 'fc:34:51:b6:35:eb',
                  'l2ng-l2-mac-age': '-',
                  'l2ng-l2-mac-flags': 'D',
                  'l2ng-l2-mac-fwd-next-hop': '0',
                  'l2ng-l2-mac-logical-interface': 'ge-0/0/23.0',
                  'l2ng-l2-mac-rtr-id': '0',
                  'l2ng-l2-mac-vlan-name': 'vlan-100'},
                { 'l2ng-l2-mac-address': 'fc:34:51:e1:35:ea',
                  'l2ng-l2-mac-age': '-',
                  'l2ng-l2-mac-flags': 'D',
                  'l2ng-l2-mac-fwd-next-hop': '0',
                  'l2ng-l2-mac-logical-interface': 'ge-0/0/23.0',
                  'l2ng-l2-mac-rtr-id': '0',
                  'l2ng-l2-mac-vlan-name': 'vlan-100'}
            ],
            'learnt-mac-count': '59',
            'mac-count-global': '59'
        }
    }
}

$

 

 


 

[ 예제 3 ]  Juniper Switch의  장치 정보, Spec 등 조회

아래 예제는 Juniper Switch(10.1.1.2) 장치에 접속해서 Juniper Switch의 장치 정보 및 Spec을 조회한다. (아래 항목을 참고)

- Switch model name

- Switch status

- 기동하여 운영되고 있는 시간 (up time)

- Switch 장비 이중화 정보 (Master, Member, FWDD 등)

- FQDN (domain name)

- Switch 장치의 Hostname

- JunOS 정보 (os name, version)

- etc

 

 

아래와 같이 Python 예제 코드를 작성한다.

from pprint import pprint
from jnpr.junos import Device
from jnpr.junos import Device

with Device(host='10.1.1.2', user='myuser', password='mypasswd1234' ) as dev:
    pprint(dev.facts)
    pprint(dev.facts['version'])
    pprint(dev.facts['switch_style'])

 

 

위에서 작성한 Python 예제 코드를 아래와 같이 실행한다.

$  python3  my_example_3.py

{'2RE': False,
 'HOME': '/var/home/admin',
 'RE0': {'last_reboot_reason': '0x1:power cycle/failure',
         'mastership_state': 'master',
         'model': 'RE-EX2300-24T',
         'status': 'OK',
         'up_time': '6 days, 23 hours, 50 minutes, 36 seconds'},
 'RE1': None,
 'RE_hw_mi': False,
 'current_re': ['master',
                'node',
                'fwdd',
                'member',
                'pfem',
                're0',
                'fpc0',
                'localre'],
                
... 중간 생략 ...

 'version': '20.4R3-S2.6',
 'version_info': junos.version_info(major=(20, 4), type=R, minor=3-S2, build=6),
 'virtual': False}
'20.4R3-S2.6'
'VLAN_L2NG'

$

 

 


 

[ 예제 4 ]  Juniper Switch의  Shell 명령, CLI 명령을 원격으로 실행

아래 예제는 Juniper Switch(10.1.1.2) 장치에 접속해서 Juniper Switch의 shell 명령(CLI 명령)을 실행하는 코드이다.

 

아래와 같이 Python 예제 코드를 작성한다.

from jnpr.junos import Device
from jnpr.junos.utils.start_shell import StartShell


dev = Device(host='10.1.1.2', user='myadmin', password='mypasswd1234' )

ss = StartShell(dev)
ss.open()

version = ss.run('cli -c "show version | no-more"')
print(version)

ether_tbl = ss.run('cli -c "show ethernet-switching table interface ge-0/0/23 | no-more"')
print(ether_tbl)

ss.close()

 

 

위에서 작성한 Python 예제 코드를 아래와 같이 실행한다.

$  python3  my_example_4.py

True, 'cli -c "show version | no-more"\r\r\n
 fpc0:\r\n
 --------------------------------------------------------------------------\r\n
 Model: ex2300-24t\r\n
 Junos: 20.4R3-S2.6\r\n
 JUNOS OS Kernel 32-bit  [20211117.c779bdc_builder_stable_11-204ab]\r\n
... 중간 생략 ...
 '
)


(True, 'cli -c "show ethernet-switching table interface ge-0/0/23 | no-more"\r\r\n\r\n
 MAC database for interface ge-0/0/23\r\n\r\n
 MAC database for interface ge-0/0/23.0\r\n\r\n
 MAC flags (S - static MAC, D - dynamic MAC, L - locally learned, P - Persistent static, C - Control MAC\r\n
           SE - statistics enabled, NM - non configured MAC, R - remote PE MAC, O - ovsdb MAC)\r\n\r\n\r\n
 Ethernet switching table : 61 entries, 61 learned\r\n
 Vlan                MAC                 MAC         Age    Logical                NH        RTR \r\n
 name                address             flags              interface              Index     ID\r\n
 default             00:03:5a:17:07:14   D             -   ge-0/0/23.0            0         0       \r\n
 default             00:03:5a:20:02:e2   D             -   ge-0/0/23.0            0         0       \r\n
... 중간 생략 ...
 default             fc:34:5a:e1:35:ea   D             -   ge-0/0/23.0            0         0       \r\n% '
)

$

 

 

 

 

 

 

 

 

참고 문서

Junos PyEX 파이썬 패키지를 사용하여 Juniper Switch 구성 정보를 가져오기

Junos PyEX 파이썬 패키지를 사용하여 출력 내용을 구조화하기 (출력할 컬럼을 선별하여 요약본 만들기)

 

 

 

 

 


 

반응형
작성일: 2023년 9월

 

IP address 범위를 지정하여 ping 패킷(즉, ICMP Echo Request)를 보내고 싶다면, 아래 예제 스크립트처럼 작성하여 실행하면 된다.

 

#!/usr/bin/bash

for ip in $(seq 1 7);
  do ping -c 1 10.1.4.$ip;
done

 

반응형

 


 

작성일: 2024년 2월 21일

 

Shell script 작성할 때마다 책꽂이 어딘가에 있을 책을 찾는 것이 귀찮고, 인터넷에서 검색하는 것도 귀찮아서, 
내가 자주 사용하는 Shell script use case를 정리하는 것이 편하겠다는 생각이 들었다.

 

 

Bash Shell Programming 추천 문서

추천 문서 1

  • Link: https://www.lesstif.com/lpt/bash-shell-script-programming-26083916.html
  • 이 문서가 다루는 내용
    • For loop
    • Script parameter, argument ($0, $1, ... $n, $@)   // CLI를 통해서 받은 argument를 처리하는 방법
    • getopt
    • compare operator (비교 연산자, if then fi, else, elif, ... )
    • File test (디렉토리 존재 여부, 파일 존재 여부, 실행 파일 여부 등 검사)
    • String test (문자열이 공백인지 검사,  Regular expression 정규식)
    • File 읽기 (Text file을 line by line으로 읽어서 변수에 담기)
    • nohup + stdin/out redirect (스크립트를 background로 실행시켜 terminal이 끊길 때 스크립트가 종료되는 것을 방지)
    • EUID 활용법 (일반 계정, root 계정으로 실행했는지 테스트)
    • $? 변수 체크 (외부 명령을 실행 후, 그 결과가 성공/실패인지를 체크)

추천 문서 2

  • Link: https://velog.io/@markyang92/declaretypeset
  • 이 문서가 다루는 내용
    • 변수 이름 작명 규칙
    • 변수 선언, 변수 사용(값 참조)
    • eval 키워드 사용법
    • export, declear, set 키워드를 사용하는 이유 및 활용 사례
    • unset, typeset 키워드 사용 사례
    • 미리 정의된 환경 변수 (HOME, PATH, LANG, SHELL, USER, DISPLAY, PS1, PS2, LINES, LS_COLORS 등)

추천 문서 3

  • Link: https://blog.gaerae.com/2015/01/bash-hello-world.html
  • 이 문서가 다루는 내용
    • function 선언, function 참조
    • 변수 선언, 변수 사용(값 참조)
    • 예약 변수(Reserved Variable): FUNCNAME, SECONDS, SHLVL, PPID, BASH_ENV, BASH_VERSION, OSTYPE
    • 위치 매개 변수(Positional Parameters): $0,  $1,  $*,  $@,  $#
    • 특수 매개 변수(Special Parameters): $$,  $?,  $!,  $-,  $_
    • 매개 변수 확장(Parameter Expansion): ${변수:-단어}  ${변수#단어}  ${변수/%찾는단어/변경단어}
    • 배열 변수(Array Variable): ${array[3]}  ${array[@]}
    • 변수 타입 지정(Variables Revisited): declare, typeset
    • 논리 연산자(Logical Operator):  &&  -a   ||  -o
    • 산술 연산자(Arithmetic Operator):  **  +=  -=  *=  /= %=
    • 비트 연산자(Bitwise Operator):  <<   <<=  >>=  &   &=  |   |=  ~   ~  ^   ^=
    • 정수 비교(Integer Comparison):  -eq  -ne  >  -gt  >=  -ge  <  -lt  <=  -le
    • 문자열 비교(String Comparision):  =  ==  !=  <  >  -z   -n
    • 파일 비교 연산자(File Test Operator):  -e  -f  -s  -d -b  -c  -p  -nt  -ot  -ef
    • 반복문: for  while  until
    • 조건문:  if elif  else  fi
    • 선택문:  case ... esac
    • 디버깅 방법: Dry run (bash -n my-run.sh)

 


 

유용한 스크립트 예제 모음

 

Remote node의 파일을 가져와서 Local node의 파일과 동일한지 확인 후 Local 장비에 반영하거나 삭제

아래 스크립트에서 유용한 부분은

  • date(날짜) 출력 포맷을 가공
  • 여러 단어(Word)로 구성된 문자열을 cut 명령으로 자르기(cut, split, delimiter)
  • if 조건문 사용
#!/usr/bin/bash

MY_REMOTE_ACCT=my-id@my-domain.kr

CUR_DATE=`date +%+4Y-%m-%d-%H-%M-%S`
ORIGIN_FILE="my-domain.kr.zone"
REMOTE_FILE="${ORIGIN_FILE}_remote"
BACKUP_FILE="${ORIGIN_FILE}_${CUR_DATE}_bkup"
DIR_PATH=/var/cache/bind

cd $DIR_PATH

scp ${MY_REMOTE_ACCT}:${DIR_PATH}/${ORIGIN_FILE} $REMOTE_FILE

## cksum 명령의 결과가 3개의 컬럼으로 출력되는데, 
## 실제 필요한 데이터는 첫번째 column이라서 출력 내용을 cut -f 1 명령으로 잘라냄.
CKSUM_REMOTE_FILE=$(cksum $REMOTE_FILE | cut -d " " -f 1)
CKSUM_ORIGIN_FILE=$(cksum $ORIGIN_FILE | cut -d " " -f 1)

if [ $CKSUM_REMOTE_FILE -eq $CKSUM_ORIGIN_FILE ]; then
        echo "The ${CKSUM_REMOTE_FILE} and the $CKSUM_ORIGIN_FILE are equivalent"
        exit 1
fi

echo "The ${CKSUM_REMOTE_FILE} and the $CKSUM_ORIGIN_FILE are different"

cp $ORIGIN_FILE $BACKUP_FILE
cp $REMOTE_FILE $ORIGIN_FILE

systemctl restart bind9

 

 

##

## TODO: 나중에 추가로 예제를 작성하기

##

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

반응형

아래 Script는 Kuberentes Node의 IP Address를 얻어와서, 이 IP Address로 Node에 SSH 접속하여 'shutdown' 명령을 수행하는 예제이다.

 

#!/bin/bash

for ip in $(oc get nodes  -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}')
do
   echo "reboot node $ip"
   ssh -o StrictHostKeyChecking=no core@$ip sudo shutdown -r -t 3
done
반응형

Shell script를 작성하다보면, 10 ~ 60초 정도 대기하는 시간에 멀뚱히 터미널을 쳐다보는 것이 지루할 때가 있다.

지루하지 않더라도 터미널에 수십초 동안 아무것도 출력되지 않으면,

"어~ 스크립트가 중간에 hang up 상태로 빠졌나?"

이런 의심을 하게 된다.

이럴 때, 시간이 흘러가는 것처럼 숫자가 올라가거나 내려가거나(count-up, count-down) 하면 좋다.

긴 설명은 필요없고, 아래 script를 활용하면 대기하는 지루함을 줄일 수 있다.

 

##
## NOTE:
##  macOS와 Ubuntu, CentOS의 date 명령에 대한 output format 옵션이 달라서 script가 조금 다르다.
##  


##
## NOTE: Ubuntu, CentOS에서 동작하는 스크립트
##

#!/usr/bin/bash

function countdown(){
   date_start=$((`date +%s` + $1));
   while [ "$date_start" -ge `date +%s` ]; do
     left_time="$(date -u --date @$(($date_start - `date +%s`)) +%M:%S)";
     echo -ne "Countdown  $left_time \r"
     sleep 0.1
   done
}

countdown 5


---


##
## NOTE: macOS에서 동작하는 스크립트
##

#!/opt/homebrew/bin/bash

function countdown(){
   date_start=$((`date +%s` + $1));

   while [ "$date_start" -ge `date +%s` ]; do
     curr_time=$((`date +%s`))

     left_time=$(($date_start - $curr_time))
     echo -ne "Countdown  $left_time \r"
     sleep 0.1
   done
}

countdown 3

 

반응형

 

 


 

Network Programming을 하다보면, curl을 이용해서 간단하게 테스트할 일이 종종 있다.

맨날 사용하는 개발자라면, 머릿속에 curl 사용법이 있어서 금방 명령어를 만들어서 사용하겠지만 나처럼 1년에 5번 정도 사용하는 사람은 매번 검색하는 것도 일이고 시간 낭비이다. 

그래서 자주 사용하는 명령 패턴을 메모를 좀 남겨놓고 필요할 때마다 Copy & Paste 해볼까 한다.

 

설치

아래 명령처럼 설치한다.

##
## Ubuntu 리눅스
##

$ apt install curl


## CentOS, RedHat Linux(RHEL)

$ yum install curl

 

 

CURL 명령의 주요 옵션

-X  (--request)

    HTTP Method.

    예를 들어 GET(조회), POST(데이터 생성), PUT(전체 내용 변경), DELETE, PATCH(일부 내용 변경)

 

-d  (--data)

   POST, PUT 메소드로 요청시 HTTP Body에 담아서 보낼 데이터

 

-H  (--header)

   HTTP Header 항목과 값을 정의

 

 

CURL 명령 예제

 

GET Method 예제

 

##
## Example - OCP에서 Node 정보를 가져오는 명령
##

TOKEN=$(oc whoami -t)
ENDPOINT="https://api.ocp.mycluster.io:6443"
NAME="worker1.ocp.mycluster.io"

curl -k \
    -H "Authorization: Bearer $TOKEN" \
    -H 'Accept: application/json' \
    -X GET  $ENDPOINT/api/v1/nodes/$NAME/status  
    
    
##
## Example - 요청 메시지에 Data를 포함하여 전송하기
##

curl -d "mykey1=myvalue1&mykey2=myvalue2" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -X GET http://mywebserver.example/api/v1
     
     
     
##
## Example - Query Parameter 형태로 요청 메시지를 전송하는 경우
##           이 때는 특수문자 또는 공백 문자 때문에 urlencode 처리를 해야 한다.
##
## 참고로, 아래 예시는 OCP Pod의 Memory 사용량 조회하는 API를 Call하는 것이다.
##

RESPONSE_FILE=curl-metric-node-cpu.json

curl -s -k -o $RESPONSE_FILE\
    -H "Authorization: Bearer $TOKEN" \
    -H 'Accept: application/json' \
    --data-urlencode "query=instance:node_memory_utilisation:ratio{job=\"node-exporter\", instance=\"worker4.ocp.mycluster.io\", cluster=\"\"} != 0" \
    $ENDPOINT/api/v1/query

 

POST Method 예제

 

##
## Example - URL 형식의 Data 전송
##

$  curl -d "mykey1=myvalue1&key2=myvalue2" \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -X POST  http://my.webserver.example/api


##
## Example - JSON 형식의 Data 전송
##
$  curl -d '{"mykey1":"myvalue1", "mykey2":"myvalue2"}' \
        -H "Content-Type: application/json" \
        -X POST  http://my.webserver.example/api

 

 

 

반응형

Kubernetes나 Docker를 이용해서 이것저것 사용하다보면, 나중에는 사용하지 않고 남아 있는 쓰레기 image들이 있다.

이것들을 한방에 지우는 스크립트를 작성했다.

 

아래 Script는 ":5000" 문자열을 포함하는 container image만 찾아서 지운다.

#!/usr/bin/bash

REMOVE_ITEM_LIST=$(docker image ls | grep ":5000" | awk '{ printf "%s:%s\n", $1, $2 }')

for REMOVE_ITEM in $REMOVE_ITEM_LIST
do
  echo "TO Remove:  $REMOVE_ITEM"
  docker image rm $REMOVE_ITEM
done

 

+ Recent posts