반응형

 

YAML 내용 비교하기 (using Python)

 

 

`vim -d old_file new_file` 또는 diff 등 명령으로 파일에서 변경된 내용을 확인할 수 있지만,

이렇게 비교를 하면, 내용이 동일하더라도 특정 내용의 행이 위/아래로 이동한 경우에는 다른 파일로 간주된다.

즉, 내용을 비교하고 싶은데 문서의 Format이 변경된 것 때문에 다른 파일로 인식되는 문제가 생긴다.

 

따라서 서로 다른 버전의 YAML file을 비교할 때는 두 파일을 Parsing해서 구조체에 넣고, 특정 항목이 추가되었는지 또는 삭제되었는지 또는 변경되었는지를 일일히 확인하는 것이 제일 정확한다.

 

Istio project의 Utility code를 살펴보니, 이런 기능을 하는 python code가 있어서 인용해봤다.

 

https://github.com/istio/istio/blob/master/bin/diff_yaml.py

 

GitHub - istio/istio: Connect, secure, control, and observe services.

Connect, secure, control, and observe services. Contribute to istio/istio development by creating an account on GitHub.

github.com

 

 

 

혹시 GitHub에서 Folder 구조가 바뀌거나 diff_yaml.py 도구가 제거될 것을 우려해서 여기 blog에 흔적을 남긴다.

 

diff_yaml.py (source code 열람)

#!/usr/bin/env python
#
# Copyright 2018 Istio Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Compare 2 multi document kubernetes yaml files
# It ensures that order does not matter
#
from __future__ import print_function
import argparse
import datadiff
import sys
import yaml  # pyyaml

# returns fully qualified resource name of the k8s resource


def by_resource_name(res):
    if res is None:
        return ""

    return "{}::{}::{}".format(res['apiVersion'],
                               res['kind'],
                               res['metadata']['name'])


def keydiff(k0, k1):
    k0s = set(k0)
    k1s = set(k1)
    added = k1s - k0s
    removed = k0s - k1s
    common = k0s.intersection(k1s)

    return added, removed, common


def drop_keys(res, k1, k2):
    if k2 in res[k1]:
        del res[k1][k2]


def normalize_configmap(res):
    try:
        if res['kind'] != "ConfigMap":
            return res

        data = res['data']

        # some times keys are yamls...
        # so parse them
        for k in data:
            try:
                op = yaml.safe_load_all(data[k])
                data[k] = list(op)
            except yaml.YAMLError as ex:
                print(ex)

        return res
    except KeyError as ke:
        if 'kind' in str(ke) or 'data' in str(ke):
            return res

        raise


def normalize_ports(res):
    try:
        spec = res["spec"]
        if spec is None:
            return res
        ports = sorted(spec['ports'], key=lambda x: x["port"])
        spec['ports'] = ports

        return res
    except KeyError as ke:
        if 'spec' in str(ke) or 'ports' in str(ke) or 'port' in str(ke):
            return res

        raise


def normalize_res(res, args):
    if not res:
        return res

    if args.ignore_labels:
        drop_keys(res, "metadata", "labels")

    if args.ignore_namespace:
        drop_keys(res, "metadata", "namespace")

    res = normalize_ports(res)

    res = normalize_configmap(res)

    return res


def normalize(rl, args):
    for i in range(len(rl)):
        rl[i] = normalize_res(rl[i], args)

    return rl


def compare(args):
    j0 = normalize(list(yaml.safe_load_all(open(args.orig))), args)
    j1 = normalize(list(yaml.safe_load_all(open(args.new))), args)

    q0 = {by_resource_name(res): res for res in j0 if res is not None}
    q1 = {by_resource_name(res): res for res in j1 if res is not None}

    added, removed, common = keydiff(q0.keys(), q1.keys())

    changed = 0
    for k in sorted(common):
        if q0[k] != q1[k]:
            changed += 1

    print("## +++ ", args.new)
    print("## --- ", args.orig)
    print("## Added:", len(added))
    print("## Removed:", len(removed))
    print("## Updated:", changed)
    print("## Unchanged:", len(common) - changed)

    for k in sorted(added):
        print("+", k)

    for k in sorted(removed):
        print("-", k)

    print("##", "*" * 25)

    for k in sorted(common):
        if q0[k] != q1[k]:
            print("## ", k)
            s0 = yaml.safe_dump(q0[k], default_flow_style=False, indent=2)
            s1 = yaml.safe_dump(q1[k], default_flow_style=False, indent=2)

            print(datadiff.diff(s0, s1, fromfile=args.orig, tofile=args.new))

    return changed + len(added) + len(removed)


def main(args):
    return compare(args)


def get_parser():
    parser = argparse.ArgumentParser(
        description="Compare kubernetes yaml files")

    parser.add_argument("orig")
    parser.add_argument("new")
    parser.add_argument("--ignore-namespace", action="store_true", default=False,
                        help="Ignore namespace during comparison")
    parser.add_argument("--ignore-labels", action="store_true", default=False,
                        help="Ignore resource labels during comparison")
    parser.add_argument("--ignore-annotations", action="store_true", default=False,
                        help="Ignore annotations during comparison")

    return parser


if __name__ == "__main__":
    parser = get_parser()
    args = parser.parse_args()
    sys.exit(main(args))

 

 

diff_yaml.py 명령 따라하기

위 python code를 diff_yaml.py 파일명으로 저장하고, 아래와 같이 필요한 Python package를 설치하고, Run 한다.

$  pip install --upgrade pip

$  python -m pip install datadiff

$  ./diff_yaml.py /tmp/metallb-1.yaml /tmp/metallb-2.yaml

## +++  /tmp/metallb-2.yaml
## ---  /tmp/metallb-1.yaml
## Added: 1
## Removed: 1
## Updated: 0
## Unchanged: 14
+ apps/v1::Deployment_Test::controller     ## 변경된 내용을 보여준다.
- apps/v1::Deployment::controller          ## 변경된 내용을 보여준다.
## *************************

$

## 이것은 Linux diff 명령으로 확인한 내용이다.
## 실제로 yaml 내용은 변경된 것이 1개 이지만, 단지 행을 이동했다는 이유만으로
## 여러 곳이 다르다고 표기하고 있다. (즉, YAML을 사용하는 User 관점에서 보면 다 쓸데 없는 정보이다.)

$  diff /tmp/metallb-1.yaml /tmp/metallb-2.yaml
3d2
< kind: Namespace             ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
5d3
<   name: metallb-system      ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
8a7,8
>   name: metallb-system      ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
> kind: Namespace             ## <-- 실제로 이 부분은 내용이 바뀐 것이 아니라, 라인만 이동한 것이다.
352d351
< kind: Deployment
400a400
> kind: Deployment_Test       ## <-- 이 부분만 내용이 변경된 것이다.

 

위 결과를 보면, 일반적인 diff와 내용이 다르다는 것을 알 수 있다.

가장 큰 차이점은 diff_yaml.py는 수정된 Line과 Column을 출력하지 않는다. 왜냐하면, 내용 자체의 변경 여부가 관심사이지 어떤 내용이 다른 행으로 이동했는지 또는 삭제, 추가되었는지 중요하지 않기 때문이다.

 

 

 

JSON 내용 비교하기 (using Python)

 

JSON 내용을 비교하는 CLI 도구는 아래 Web Docs를 참고하길 ~~~

 

 

GitHub - xlwings/jsondiff: Diff JSON and JSON-like structures in Python

Diff JSON and JSON-like structures in Python. Contribute to xlwings/jsondiff development by creating an account on GitHub.

github.com

 

 

json-diff

Generates diff between two JSON files

pypi.org

 

 

반응형

Mac, Ubuntu, CentOS에서 Terminal을 사용하다보면, 알록달록 Color가 들어간 Text를 보게 된다.

이렇게 색이 들어간 글자를 만들려면, ANSI escape code라는 표준 기술을 활용해야 한다.

ANSI escape code에 관한 자세한 내용은 이 글의 끝 부분에 있는 Reference Web Docs를 보길~~~

 

간단하게 터미널의 글자에 색을 넣는 방법만 보면 아래와 같다.

 

Mac iTerm2 터미널에 글자 컬러 넣기

 

 

ANSI escape code 확인하는 C source code

각 ANSI code가 어떤 color, font, effect를 보여주는지 확인하고 싶다면, 아래와 같이 짧게 코드를 작성해서 돌려보면 바로 감(느낌)을 찾을 수 있다.

##
## File name:  main.c
##

#include <stdio.h>

int main(void)
{
  int i, j, n;

  for (i = 0; i < 11; i++) {
    for (j = 0; j < 10; j++) {
      n = 10*i + j;
      if (n > 108) break;
      printf("\033[%dm %3d\033[m", n, n);
    }
    printf("\n");
  }
  return 0;
}

 

위 C source code를 작성하고, 아래와 같이 gcc 명령으로 compile하고 실행해보면, 각 ANSI code의 숫자가 어떤 색을 표현하는지 알 수 있다.

 

ANSI escape code 확인하는 Python source code

각 ANSI code가 어떤 color, font, effect를 보여주는지 확인하고 싶다면, 아래와 같이 짧게 코드를 작성해서 돌려보면 바로 감(느낌)을 찾을 수 있다.

##
## File name:  main.py
##

import sys
for i in range(0, 16):
    for j in range(0, 16):
        code = str(i * 16 + j)
        sys.stdout.write(u"\u001b[38;5;" + code + "m " + code.ljust(4))
    print u"\u001b[0m"

 

위 Python source code를 작성하고, 아래와 같이 실행해보면, 각 ANSI code의 숫자가 어떤 색을 표현하는지 알 수 있다.

 

 

 

import sys
for i in range(0, 16):
    for j in range(0, 16):
        code = str(i * 16 + j)
        sys.stdout.write(u"\u001b[48;5;" + code + "m " + code.ljust(4))
    print u"\u001b[0m"

 

 

 

 

ANSI escape code의 역사, 표준 정보, Example 등 자세한 내용은 아래 Reference Web Docs를 참고하길 ~~~

 

Reference

 

ANSI escape code - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Method used for display options on video text terminals ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on

en.wikipedia.org

 

 

ASCII Art Archive

A large collection of ASCII art drawings and other related ASCII art pictures.

www.asciiart.eu

 

※ 아래 Web Docs는 CLI 만들기에 필요한 다양한 Trick과 Tip이 있다.

※ CLI에 Progress Bar, Processing Status, Percentile 등 효과를 넣고 싶다면 아래 Web Docs를 참고할 것 !!!

 

Build your own Command Line with ANSI escape codes

Build your own Command Line with ANSI escape codes Everyone is used to programs printing out output in a terminal that scrolls as new text appears, but that's not all your can do: your program can color your text, move the cursor up, down, left or right, o

www.lihaoyi.com

 

반응형

Mac OS의 iTerm2 터미널에서 한글 파일명을 작성하면, 아래 화면처럼 음소(자음, 모음)가 분리되면서 깨져 보이는 현상이 있다.

(자음, 모음 또는 초성, 중성, 종성이 분리되는 현상)

iterm2 설치 후, 아무런 설정을 하지 않고 기본 설정을 사용하면 이렇게 한글이 깨져 보인다.

한글 초성, 중성, 종성이 모두 분리되서 출력된다.

 

이 때,  iTerm2의 설정 화면을 보면 아래 화면처럼 [Unicode normalization form] 항목이 NFD로 되어 있을 것이다.

 

iTerm2 App의 [Preferences] / [Text] 설정

 

NFD를 다른 값으로 설정하면, 한글이 정상 모양으로 잘 출력된다. (아래 그림을 참고)

내 Macbook의 경우에는 [None, NFC, HFS+] 중에서 아무거나 선택해도 효과는 동일하게 잘 동작했다.

그런데, 다른 Mac User 중에서는  NFC만 잘 된다고 하는 User도 있는 걸로 봐서, 찜찜하면 NFC로 설정하는 것이 좋을 듯!!

 

iTerm2 App의 [Preferences] / [Text] 설정

 

한글 초성, 중성, 종성이 합쳐져서 보여진다.

 

Unicode normalization form을 NFD에서 NFC로 설정해서 잘 동작하니까 좋긴 한데, NFD, NFC가 무엇인지 잘 모르겠다.

NFD, NFC에 관한 자세한 설명은 아래 Web Docs를 참고하길 ~~~

 

Reference

 

 

유니코드 정규화(Unicode Normalization Form) 문제

 

www.hungrydiver.co.kr

 

반응형

 

 


작성일: 2024년 3월 21일

 

 

일반적으로 자주 사용하는 명령은 kubectl cheat sheet를 보는 것을 추천 !

  https://kubernetes.io/docs/reference/kubectl/cheatsheet/

 

 

Pod 이름 가져오기

$  kubectl get pod -l app=myapp  -o jsonpath='{.items[0].metadata.name}'
myapp-6c68579c55-xk6cm

 

Pod 내부에서 bash shell command 수행하기

Script 중간에 kubectl 명령을 이용하는 경우에

아래 예제처럼 --output=jsonpath 옵션을 활용하면 편하면서 복잡한 로직을 만들 수 있다.

##
## jsonpath를 이용하면, 복잡한 script를 작성할 때 편하다.
##

$  kubectl exec -it  -n myns  $(kubectl get  -n nrf  pod -l app=myapp --output=jsonpath="{.items[0].metadata.name}") -c mybox -- bash

 

 

 

Service 리소스의 Public IP address (EXTERNAL-IP) 값 가져오기

jsonpath 옵션을 사용해서 IP Address를 추출하기 전에

아래 명령 예제와 같이 JSON 포맷으로 전체 내용을 출력해본다.

이 전체 JSON 출력 내용을 보면, 내가 추출하고 싶은 Item의 JSON Path를 짐작할 수 있다.

예를 들어, Service resource의 IP "193.173.24.21" 값을 추출하고 싶다면

.status.loadBalancer.ingress 경로로 JSONPATH를 지정하면 된다.

$ kubectl get  -n myns  svc mysvc  -o json

{
    "apiVersion": "v1",
    "kind": "Service",
    "metadata": {

... 중간 생략 ...

    },
    "status": {
        "loadBalancer": {
            "ingress": [
                {
                    "ip": "193.173.24.21"
                }
            ]
        }
    }
}

$

 

위 JSON 전체 내용을 보고, JSONPATH를 확인 후 아래 명령을 입력한다.

 

$ kubectl get  -n myns  svc mysvc  --output=jsonpath="{.status.loadBalancer.ingress[0].ip}"

193.173.24.21

$

 

 

 


 

반응형

GitHub에 있는 AWX README.MD 설명을 따라서 설치해보면, 설치 중간에 에러가 발생해서 그런 부분을 회피하면서 설치하는 방법을 메모해봤다.

아래 명령어를 따라서 수행하면, 에러 없이 AWX가 잘 설치된다.

 

 

※ 주의

AWX는 Version 17.1.0 이전의 설치 방법과 Version 18.0.0 이후의 설치 방법이 전혀 다르다.

이 문서의 설치 방법은 Version 15.0.0 ~ 17.1.0만 유효하다.  다른 AWX Version은 이 방법대로 설치할 수 없다.

 

 

설치 환경

  • OS: CentOS 7 (VM on KVM)
  • CPU: 4 Core
  • MEM: 8GB  (실제로 설치해보니, 4GB만 있어도 충분하다. 단, 다른 App이 이 Host에서 같이 운영된다면 넉넉하게 8GB로 구성하는 것이 좋다)
  • AWX Version: 17.1.0

 

설치 절차 (명령 따라하기)

Reference Docs:  https://github.com/ansible/awx/blob/17.1.0/INSTALL.md

 

GitHub - ansible/awx: AWX Project

AWX Project. Contribute to ansible/awx development by creating an account on GitHub.

github.com

 

위 github의 INSTALL.md를 따라해도 되고, 위 문서를 명령만 요약한 아래 명령만 따라해도 잘 설치된다.

(긴 글을 읽는 것이 귀찮다면, 아래 명령을 쭉~  따라하는 것이 마음이 편할 듯)

 

##  보안 수준을 낮춘다.
$  systemctl  stop     firewalld
$  systemctl  disable  firewalld

$  cat  /etc/sysconfig/selinux
...
SELINUX=disabled
...
$  setenforce 0
$  sestatus

##  Ansible 환경 구성
$  yum install -y epel-release
$  yum install -y yum-utils device-mapper-persistent-data lvm2 ansible git python-devel python-pip python-docker-py vim-enhanced wget net-tools

##  Docker Install은 Docker.io를 참고
##     https://docs.docker.com/engine/installation/

$  yum install dnf -y
$  dnf install git gcc gcc-c++ ansible nodejs gettext device-mapper-persistent-data lvm2 bzip2 python3-pip -y

$  dnf install -y 'dnf-command(config-manager)'

$  pip3 install --upgrade setuptools
$  pip3 install setuptools-rust
$  pip3 install wheel
$  pip3 install "pip>=20"
$  pip3 install docker-compose

## inventory file에 넣을 key 생성
$  openssl rand -base64 30
lv4Y2WfbqRrI/+/X+R9mFN/r7BsGTdEdZR7bG8Re

## AWX PKG를 다운로드하고, 설정하기
$  cd  /root/andrew
$  wget https://github.com/ansible/awx/archive/17.1.0.tar.gz
$  tar xf 17.1.0.tar.gz
$  ln -s awx-17.1.0/ awx
$  mkdir -p /var/lib/pgdocker
$  cd /root/andrew/awx/installer

## inventory file 설정하기
$  cat  inventory
localhost ansible_connection=local ansible_python_interpreter="/usr/bin/env python3"

[all:vars]

dockerhub_base=ansible

awx_task_hostname=awx
awx_web_hostname=awxweb
postgres_data_dir="~/.awx/pgdocker"
host_port=80
host_port_ssl=443
docker_compose_dir="~/.awx/awxcompose"

pg_username=awx
pg_password=awxpass
pg_database=awx
pg_port=5432

pg_admin_password=password

admin_user=admin
admin_password=password

create_preload_data=True

## 위에서 openssl 명령어로 만든 key를 이곳에 붙여 넣는다.
secret_key=lv4Y2WfbqRrI/+/X+R9mFN/r7BsGTdEdZR7bG8Re

awx_alternate_dns_servers="8.8.8.8,1.1.1.1"

project_data_dir=/var/lib/awx/projects

$  

## Ansible Playbook을 통해 deploy
$ ansible-playbook -i inventory install.yml
...

##
## NOTE: 오랜 시간이 걸리기 때문에 Terminal Window를 1개 더 열어서
##       아래와 같이 container image가 생성되고, container가 구동되는 과정을 확인하는 것이 좋다.
##       (나는 대략 5분 정도 시간이 걸렸다)
##       시간이 오래 걸리는 이유는 container image를 internet을 통해서 pulling해야 하기 때문이다.
##

$  docker image ls
REPOSITORY                TAG        IMAGE ID       CREATED         SIZE
tianon/postgres-upgrade   10-to-12   f81b5bb17600   4 days ago      403MB
postgres                  10         5949e493c793   4 days ago      200MB
postgres                  12         905214d6fdc5   4 days ago      371MB
redis                     latest     7faaec683238   4 weeks ago     113MB
centos                    8          5d0da3dc9764   2 months ago    231MB
ansible/awx               17.1.0     599918776cf2   8 months ago    1.41GB

$  docker container ls -a
CONTAINER ID   IMAGE                COMMAND                  CREATED         STATUS         PORTS                                   NAMES
df511c34e75e   ansible/awx:17.1.0   "/usr/bin/tini -- /u…"   5 minutes ago   Up 5 minutes   8052/tcp                                awx_task
0d4247b6dd14   ansible/awx:17.1.0   "/usr/bin/tini -- /b…"   5 minutes ago   Up 5 minutes   0.0.0.0:80->8052/tcp, :::80->8052/tcp   awx_web
d4f4b8d44901   postgres:12          "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes   5432/tcp                                awx_postgres
e1af073eccee   redis                "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes   6379/tcp                                awx_redis

 

위와 같이 awx, redis, postgres 등 container가 구동되면, Web Browser를 통해서 AWX Web UI에 접속한다.

AWX Login View
AWX Web UI

 

반응형

 

2019년 3월에 작성된 Web Docs이지만, 오늘(2021년 11월) 따라서 수행해봤는데 잘 동작한다.

 

 

https://kubernetes.io/blog/2019/03/15/kubernetes-setup-using-ansible-and-vagrant/

 

Kubernetes Setup Using Ansible and Vagrant

Author: Naresh L J (Infosys) Objective This blog post describes the steps required to setup a multi node Kubernetes cluster for development purposes. This setup provides a production-like cluster that can be setup on your local machine. Why do we require m

kubernetes.io

 

 

위 블로그를 읽으면서, 나한테 필요한 만큼 playbook을 다시 작성했다.

아래 Playbook의 내용을 간략하게 설명하면,

 

  1. Kubernetes Master Node에서  `kubeadm token create --print-join-command`를 수행하여 새로운 Node Join을 위한 `kubeadm join ....` 명령행을 생성하고,
    이렇게 얻은 kubeadm 명령행을 'join-command' 스크립트 파일에 Dump한다.
  2. 바로 위에서 얻은 'join-command'를 새롭게 추가할 Worker Node의 /tmp/join-command.sh 경로에 복사하고 실행(Run)한다.
    그러면 새로운 Worker Node가 Master Node에 추가(Join)될 것이다.

 

단, 아래 Playbook YAML 파일에서 inventory(즉, hosts를 정의한 항목)을 생략했으니까 그 부분을 감안하고 보면 좋을 듯~~~

 

 

## 위 Web docs에서 중요한 부분만 발췌했다.
## 원문 그대로는 아니고, 내가 필요한 만큼 각색했다.  

## Filename: my-playbook.yaml

- name: Joining Job Step 1 on Master node
  hosts: kubemaster
  tasks:
    - name: Generate join command
      command: kubeadm token create --print-join-command
      register: join_command
    - name: Copy join command to local file
      local_action: copy content="{{ join_command.stdout_lines[0] }}" dest="./join-command"

- name: Joining Job Step 2 on Worker node
  hosts: kubeworker
  tasks:
    - name: Copy the join command to server location
      copy: src=join-command dest=/tmp/join-command.sh mode=0777
    - name: Join the node to cluster
      command: sh /tmp/join-command.sh

 

반응형

Concept

  • Control node: Ansible을 실행하는 Node
  • Managed node: Ansible 관리 대상이 되는 Node

 

Installation

$ sudo yum install epel-release
$ sudo yum install ansible

## Adding Ansible command shell completion
##  -> 이것은 편의를 위한 기능이니, 꼭 설치할 필요는 없다
$ sudo yum install python-argcomplete
$ sudo activate-global-python-argcomplete

## Let's check the version
$  ansible --version
ansible 2.9.25
...

## host inventroy file을 편집
$ cat /etc/ansible/hosts
...
[kubeworker]
10.10.12.64
10.10.12.71

[mynode]
10.10.12.64
...
$

##
## TEST for installation
##

$ ansible all --list-hosts
  hosts (2):
    10.10.12.64
    10.10.12.71
    
$ ansible all -m ping
10.10.12.64 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
10.10.12.71 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.10.12.71 port 22: No route to host",
    "unreachable": true
}
$

 

Run example playbook - 1

##
## Run this playbook on control node
##

$  cat  playbook-example.yaml
---
- name: Add_sample_contents_to_file
  hosts: mynode

  tasks:
  - name: Add sample contents to file
    blockinfile:
      path: /root/sample_a.txt
      block: |
        [my test section]
        best contents
        greeting=hello       
$
$  ansible-playbook playbook-example.yaml

PLAY [Add_sample_file] ************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************
ok: [10.10.12.64]

TASK [Add sample file] ************************************************************************************************************
changed: [10.10.12.64]

PLAY RECAP ************************************************************************************************************************
10.10.12.64                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

$

##
## Check result of test on managed node(10.10.12.64)
##

$  cat  /root/sample_a.txt
# BEGIN ANSIBLE MANAGED BLOCK
[my test section]
best contents
greeting=hello
# END ANSIBLE MANAGED BLOCK
$

 

Run example playbook - 2

##
## Run this playbook on control node
##

$  cat  playbook-example-2.yaml
---
- name: Install pkg and create a file
  hosts: mynode

  tasks:
  - name: Install kubernetes pkg
    yum: name=tree state=present
    when: ansible_distribution == 'CentOS'
  - name: Run my command
    command: "touch /root/mydate.txt"
    when: ansible_distribution == 'CentOS'     
$
$  ansible-playbook playbook-example-2.yaml
...
$

##
## Check result of test on managed node(10.10.12.64)
##

$  which tree
/usr/bin/tree

$  ls /root/mydate.txt
/root/mydate.txt

 

Tip:   특정 User로 Ansible 실행

managed node에서 특정 user(예: root)로 실행되길 원한다면, 아래와 같이 설정 파일을 작성한다.

$  cat  /etc/ansible/ansible.cfg
...

[privilege_escalation]
become=True
become_method=sudo
become_user=root        ## Root user로 명령을 수행한다.
become_ask_pass=False   ## password를 묻지 않고 명령을 수행한다.

...

$

 

 

 

Reference

https://young-dev.com/infra/ansible_02/#

 

[Ansible] Ansible-Playbook 사용하기 #2

Ansible-Playbook 활용

young-dev.com

 

 

 

 

반응형

 

 

사람의 개입을 최소화하면서 OS를 자동 설치하는 절차를 설명하겠다.

OS를 자동 설치하는 큰 흐름은 아래 그림과 같다.

 

 

 

OS 자동 설치 절차 개요

 

DHCP, TFTP를 이용한 PXE Booting 절차

 

 

 

PXE 개념

 

PXE(Preboot Execution Environment)는 Network를 통해 OS Booting에 필요한 정보를 전달하기 위한 기술이다.

 

 

PXE를 동작시키기 위한 구성 요소

  • Server 구성 요소
    • DHCPD: DHCP Server 역할을 수행.  Client Node에게 설정해줘야할 Network 구성 정보를 전달해준다.
    • TFTP: Client의 PXE Boot image를 배포한다.
    • FTP: PXE booting 이후, OS Image 및 SW PKG를 설치할 때 필요한 File을 배포한다.
    • Kickstart: Client가 OS를 설치할 때, Kickstart 설정 파일을 읽어서 OS 설치 작업을 자동화한다. (Network 설정, 특정 PKG 설치 여부 결정, Account 생성, 인증서 복사 등)
  • Client 구성 요소
    • BIOS/NIC: BIOS에서 PXE boot를 지원해야 한다.

 

 

 

준비 작업 (Server Software PKG 설치 + 설정 파일 작성)

 

Server PKG 설치

$ yum install -y  dhcpd tftp ttfp-server xinetd vsftpd syslinux
$ systemctl enable --now dhcpd
$ systemctl enable --now tftp
$ systemctl enable --now xinetd
$ systemctl enable --now vsftpd

 

 

DHCPD Server 설정

$  cat /etc/dhcp/dhcpd.conf

...

allow booting;
allow bootp;
allow unknown-clients;

subnet 10.10.12.0 netmask 255.255.255.0 {
  range           10.10.12.64 10.10.12.79;
  option routers  10.10.12.1;
  option broadcast-address 10.10.12.255;
  option subnet-mask 255.255.255.0;
  option domain-name-servers 10.10.100.2;
  get-lease-hostnames true;
  next-server     10.10.12.33;              ## NOTE: TFTP Server 주소를 설정
  filename        "pxelinux.0";             ## NOTE: Boot Image 정보를 설정
}

...

 

 

TFTP Server 설정

$  cat  /etc/xinetd.d/tftp

service tftp
{
	socket_type		= dgram
	protocol		= udp
	wait			= yes
	user			= root
	server			= /usr/sbin/in.tftpd
	server_args		= -s /var/lib/tftpboot   ## NOTE: boot image file을 저장할 directory
	disable			= no                     ## NOTE: 'no'로 변경
	per_source		= 11
	cps			= 100 2
	flags			= IPv4
}

 

 

FTP Server 설정

## 모든 client가 vsftpd에 access하는 것을 허용하기 위한 설정
## (ID, Password 없이 FTP server에 access하는 것이 가능)

$  setsebool -P allow_ftpd_full_access 1

 

 

syslinux file 복사

##  Client에게 전달할 syslinux boot loader 파일을 TFTP 서버 폴더에 복사한다.

$  cp /usr/share/syslinux/pxelinux.0  /var/lib/tftpboot
$  cp /usr/share/syslinux/menu.c32    /var/lib/tftpboot
$  cp /usr/share/syslinux/memdisk     /var/lib/tftpboot
$  cp /usr/share/syslinux/mboot.c32   /var/lib/tftpboot
$  cp /usr/share/syslinux/chain.c32   /var/lib/tftpboot

 

 

PXE Menufile 작성

 

##  TFTP 서버의 /var/lib/tftpboot/pxelinux.cfg/default 파일에 아래와 같은 내용을 작성한다.

$  cat  /var/lib/tftpboot/pxelinux.cfg/default

default menu.c32
prompt 0
timeout 30

menu title Homelab PXE Menu

label centos7_x64
  menu label CentOS 7_X64
  kernel /networkboot/centos/vmlinuz
  append initrd=/networkboot/centos/initrd.img inst.repo=ftp://10.10.12.33/pub/centos ks=ftp://10.10.12.33/pub/centos/centos7.cfg

 

 

Linux OS image file 준비

##  FTP server에 Linux OS image file을 복사하는 절차

$  mkdir  /media/iso
$  mount  /myhome/CentOS-7-x86_64-2009.iso  /media/iso
$  mkdir  -p  /var/ftp/pub/centos
$  cp -r  /media/iso/*  /var/ftp/pub/centos/
##  준비 작업을 마무리했다면, FTP 서버에 접근 가능한지 아래와 같이 확인한다.
##  (가능하면 Remote Node에서 테스트할 것)

$  curl ftp://x-node.hub.cncf/pub/centos/
-rw-r--r--    1 0        0              14 Oct 29  2020 CentOS_BuildTag
drwxr-xr-x    3 0        0              35 Oct 26  2020 EFI
-rw-rw-r--    1 0        0             227 Aug 30  2017 EULA
-rw-rw-r--    1 0        0           18009 Dec 09  2015 GPL
drwxr-xr-x    2 0        0              43 Oct 26  2020 LiveOS
drwxr-xr-x    2 0        0          225280 Nov 04  2020 Packages
-rw-rw-r--    1 0        0            1690 Dec 09  2015 RPM-GPG-KEY-CentOS-7
-rw-rw-r--    1 0        0            1690 Dec 09  2015 RPM-GPG-KEY-CentOS-Testing-7
-r--r--r--    1 0        0            2883 Nov 04  2020 TRANS.TBL
-rwxr-xr-x    1 0        0            1833 Nov 09 10:16 centos7-graphical-server.cfg
-rwxr-xr-x    1 0        0            1708 Nov 10 14:37 centos7-ok-11-10.cfg
-rwxr-xr-x    1 0        0            5462 Nov 11 10:16 centos7-ok-11-11-2nd.cfg
-rwxr-xr-x    1 0        0            3381 Nov 11 01:39 centos7-ok-11-11.cfg
-rwxr-xr-x    1 0        0            5633 Nov 11 16:14 centos7-ok-11-12.cfg
-rwxr-xr-x    1 0        0            5739 Nov 12 03:09 centos7.cfg
drwxr-xr-x    3 0        0              57 Oct 26  2020 images
drwxr-xr-x    2 0        0             198 Nov 02  2020 isolinux
drwxr-xr-x    2 0        0            4096 Nov 04  2020 repodata
##  TFTP server 저장소에 PXE kernel image file을 복사하는 절차


$  mkdir  /var/lib/tftpboot/networkboot/centos
$  cp  /var/ftp/pub/centos/images/pxeboot/{inird.img,vmlinuz}  /var/lib/tftpboot/networkboot/centos/

 

 

Kickstart Configuration File 작성

centos7.cfg 파일의 내용이 길어서 보기 어려울 수 있으나, 원래 OS 패키지에 기본 제공되는 centos7.cfg에 일부 내용만 수정하면 된다.

(참고:  /root/centos7.cfg 같은 곳에 OS 배포판에서 제공하는 기본 kickstart configuration 파일이 있다)

 

$  cat  /var/ftp/pub/centos/centos7.cfg 

## System authorization information

auth --enableshadow --passalgo=sha512

## Andrew
##  Use network installation
url --url="ftp://10.10.12.33/pub/centos/"

## Use graphical install
#graphical
text

## Run the Setup Agent on first boot
firstboot --enable
ignoredisk --only-use=vda

## Keyboard layouts
keyboard --vckeymap=us --xlayouts='us'

## System language
lang en_US.UTF-8

## Network information
network  --bootproto=dhcp --device=eth0 --nameserver=10.10.12.2,10.10.12.3 --noipv6 --activate

## Root user's password
## NOTE:  Run this command `openssl passwd -1 mysmilepass`
##        and use the result string as a password for user.
rootpw --iscrypted $1$HC34jk4576jk23j4kljk5l6jk2j345kj

## System services
services --disabled="chronyd"

## System timezone
timezone Asia/Seoul --isUtc --nontp

## NOTE:  Run this command `openssl passwd -1 myhappypass`
##        and use the result string as a password for user.
user --groups=wheel --name=sejong --password=$1$HC34jk4576jk23j4kljk5l6jk2j345kj --iscrypted --uid=1000 --gecos="sejong" --gid=1000

## X Window System configuration information
#xconfig  --startxonboot

## System bootloader configuration
bootloader --append=" crashkernel=auto" --location=mbr --boot-drive=vda
autopart --type=lvm

## Partition clearing information
clearpart --all --initlabel --drives=vda

## Install software application
%packages
@^minimal
@base
@core
kexec-tools
net-tools
chrony
%end

## Enable KDUMP
%addon com_redhat_kdump --enable --reserve-mb='auto'
%end

## Policy of user password
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end


##
## NOTE (By Andrew)
##  Refer to  https://docs.centos.org/en-US/centos/install-guide/Kickstart2/#sect-kickstart-postinstall
##
%post --interpreter=/bin/bash

## NOTE (By Andrew)
myhostname=$(nslookup  $(hostname -I | awk '{print $1}') 10.10.12.30 | awk '{print $4}' |  cut -d '.' -f1)
hostname $myhostname
echo $myhostname > /etc/hostname

## Add authorized_keys for root user
mkdir  --mode=0700 /root/.ssh

cat << EOF > /root/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yaskdjfkjk34634jkdkfjg.....= sejong@MacBook-Pro.local
EOF

chown  -R root:root  /root/.ssh
chmod  0600  /root/.ssh/authorized_keys

## Add authorized_keys for sejong user
mkdir  --mode=0700 /home/sejong/.ssh

cat << EOF > /home/sejong/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yaskdjfkjk34634jkdkfjg.....= sejong@MacBook-Pro.local
EOF

chown  -R sejong:sejong  /home/sejong/.ssh
chmod  0600  /home/sejong/.ssh/authorized_keys

##
## Install Docker, Kubernetes
##
modprobe br_netfilter

cat <<EOF | tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sysctl --system

## disable swap space
sed -i '/swap/d' /etc/fstab
swapoff -a

## disable firewalld
systemctl stop firewalld
systemctl disable firewalld

## Install Docker container runtime
yum install -y yum-utils
yum-config-manager  --add-repo  https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
systemctl  enable  docker
systemctl  start   docker

mkdir -p /etc/docker
cat <<EOF | tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "insecure-registries": ["myhost.cncf:8080", "yourhost.cncf:8080"]
}
EOF

systemctl daemon-reload
systemctl restart docker

## Install kubeadm, kubelete
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF

setenforce 0
sed  -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
yum  install  -y kubelet kubeadm kubectl  --disableexcludes=kubernetes
systemctl enable --now kubelet

chmod +x /etc/rc.d/rc.local
systemctl enable --now  rc-local.service

echo "END OF POST-SCRIPT" > /root/kickstart.log

%end


## Andrew
##  reboot after finishing installation
reboot

 

 

Ansible Playbook 작성 (Option)

만약, kickstart configration만으로 OS 초기화/설정하기 어려운  복잡한 Job이 있다면, 아래 예시처럼 Ansible playbook을 작성해서 좀 더 복잡하고 지능적인 처리가 가능하다.

 

## playbook-kube-cluster-join.yaml

- name: Joining Job Step 1 on Master node
  hosts: kubemaster
  tasks:
    - name: Generate join command
      command: kubeadm token create --print-join-command
      register: join_command
    - name: Copy join command to local file
      local_action: copy content="{{ join_command.stdout_lines[0] }}" dest="./join-command"

- name: Joining Job Step 2 on Worker node
  hosts: kubeworker
  tasks:
    - name: Copy the join command to server location
      copy: src=join-command dest=/tmp/join-command.sh mode=0777
    - name: Join the node to cluster
      command: sh /tmp/join-command.sh

 

 

 

 


 

준비 작업은 끝났다.

공장 초기화된 컴퓨터, 또는 VirtualBox 프로그램, 또는 KVM/Qemu의 Virtual Manager로 아래 예제처럼 Machine Start만 하면 된다.

 

 

OS  자동 설치 실행

 

나는 KVM/Qemu + Virtual Manager 조합으로 VM을 만드는 것을 좋아하기 때문에 KVM을 예시로 들었다.

Hypervisor는 어떤 것을 사용하든 PXE로 부팅하는 기능은 동일하기 때문에, 다른 Hypervisor를 사용하더라도 아래 절차는 비슷하다.

 

깡통 컴퓨터의 Booting Option 메뉴에서 또는 VM을 새로 생성하면서 PXE 옵션을 선택한다.

 

VM 생성시 PXE Network Boot 옵션 활성화

 

위와 같이 PXE boot 옵션을 선택 후, booting을 시도하면 아래 화면처럼 DHCP 서버에서 Network 주소 및 TFTP 정보를 가져와서 TFTP에서 boot image file을 가져온다. 이후부터는 자동으로 OS를 설치하고, kickstart confiration file에 있는 OS 설정 작업을 자동 수행한다.

 

PXE Booting 화면

 

 

 

Reference

 

'청년정신'님이 2019년 4월에 작성한 'PXE 기반의 CentOS  자동 구축' 블로그가 제일 잘 정리된 글 같다.

 

 

PXE 기반의 CentOS 설치 1부

 

 

CentOS 강좌 PART 2. 10 PXE기반의 CentOS 서버 자동 구축 1편

CentOS 강좌 PART 2. 10 PXE기반의 CentOS 서버 자동 구축 1편 [ PXE 개념 소개 및 구성요소 ] PXE (Preboot Excution Environment) 기반의 운영체제 자동설치 방법은 앞서 강좌에서 소개한 다양한 서버 구축 기..

youngmind.tistory.com

 

 

PXE 기반의 CentOS 설치 2부

 

 

CentOS 강좌 PART 2. 10 PXE기반의 CentOS 서버 자동 구축 2편

CentOS 강좌 PART 2. 10 PXE기반의 CentOS 서버 자동 구축 2편 "CentOS 강좌 PART 2. 10 PXE기반의 CentOS 서버 자동 구축 1편" 에 이어서… 10. KickStart 구성 먼저 kickstart 파일을 구성한다. Kickstart 파일..

youngmind.tistory.com

 

 

추가로, CentOS를 kickstart를 이용해서 자동 설치하려는 경우라면, 아래 Install guide를 참고하면 좋다.

 

 

Kickstart Installations :: CentOS Docs Site

Kickstart installations offer a means to automate the installation process, either partially or fully. Kickstart files contain answers to all questions normally asked by the installation program, such as what time zone you want the system to use, how the d

docs.centos.org

 

+ Recent posts