반응형

 

작성일: 2024년 4월 29일

 

 

Keychron C1 키보드를 구입한지 3년이 지났다.

그 전에는 "Apple.com"에서 주문한 12만원짜리 Magic Keyboard(2021년형)을 사용했다.

두 제품의 타건감 차이는 극과 극이다.

이미 나는 기계식 키보드에 길들여져서인지, Magic Keyboard(매직 키보드)에 적응하지 못했다.

그리고 일반 기계식 키보드는 자판 배열이 맥북과 달라서 싫었다.

맥북과 자판 배열이 같고, 기계식 키보드(적축, 갈축 등)를 찾다보니 Keychron 제품이 눈에 들어왔다.

 

Keychron 제품에 관해서 궁금하다면 아래 웹 사이트를 방문하시길~~~

  https://keychron.kr 

 

 

간략하게 이 회사에 대해 설명하면;

 

Keychron은 2017년에 키보드 제조 분야에서 풍부한 경험을 가진 키보드 애호가 그룹에 의해 설립.

이 애호가 그룹은 디자이너, 마케터 및 생산 전문가로 구성됨.

핵심 팀원:

  - Will Ye: 키보드 생산 및 관련 비즈니스에서 9년의 경험을 가지고 있음

  - Sven Zhu: 산업 디자인에서 10년의 경험을 가지고 있음

이 회사는 미니멀한 디자인으로 가장 정교한 기계식 키보드를 만드는 것에 주력.

회사 주소: 

  • France Office:          1, Esplanade Miriam Makeba, 69100 Villeurbanne, France
  • Hong Kong Office:  13/F, Prosper Commercial Bldg., 9 Yin Chong Street, Kowloon, Hong Kong

 


키크론 키보드 타건샵

keychron.kr 홈페이지에 전국의 타건샵이 소개되어 있다.

서울, 경기도 쪽만 보면 아래 타겁샵이 있다.

 

  • 일렉트로마트 스타필드 수원점 (*) <-- 키크론 키보드 종류별로 있음. 데모 마우스 제품도 많음.
  • 일렉트로마트 스타필드 하남점 (*)
  • ---
  • 일렉트로마트 이마트 평촌점
  • 일렉트로마트 이마트 왕십리점
  • 일렉트로마트 이마트 영등포점
  • 일렉트로마트 이마트 성남점
  • ---
  • 일렉트로마트 오산점
  • 일렉트로마트 킨텍스점
  • 일렉트로마트 마포점
  • 더현대 서울 솔플레이점

 


사용 후기(리뷰)

나는 10년 전부터 5만원 대, 10만원 대, 20만원 대 기계식 키보드를 다양하게 사용하고 있었다.

지금도 사무실, 집에서 쓰고 있는 기계식 키보드는 4 종류이다.

그렇다면, 키크론(Keychron)에 대한 나의 평가는?

나의 답변: 만족한다!

 

기계식 키보드의 스위치 종류(제조사, 브랜드, 적/갈/흑/청축)에 따라 선호도가 다르기 때문에 [ 좋다, 고급지다, 저급하다 ]와 같은 평가를 하기는 거시기하고, 두루뭉술하게 키크론 키보드를 평가한다면 무난하게 평범한 기계식 키보드이다.

 

나는 Keychron Keyboard에 만족해서 우선 사무실에서 1개 구입하고, 1주일 후에 집에서 사용할 Keyboard를 한 개 더 구입했다.

지금은 사무실, 집에서 모두 Keychron Keyboard만 사용한다. (모두 적축으로 구입했음)

 

구입하고, 며칠 지난 후에 다시 Keychron Web site에 접속해보니, 레트로 버전을 새롭게 올렸던데... 솔직히 내 눈에는 레트로 키보드가 더 예뻐보인다.  한 달만 더 참았다 구입할껄하는 아쉬움이 남는다.

 

아주 솔직한 Keychron에 대한 내 생각은;

2021년 여름까지만 해도 비싸다는 인식만 있고, 품질이나 타건감은 5~10만원 정도의 기계식 키보드 수준이었다.

Apple 오프라인 매장에서 샘플 키보드를 사용해봐도, 이 정도의 키보드를 10만원 넘는 비용을 지불하면서 구입한다는 것은 미련한 짓이라 생각했는데...

2021년 가을부터 생각이 바뀌었다. 가격은 내려갔고, 품질은 더 좋아졌다. 특히 적축은 꽤 만족도가 높은 수준까지 왔다.

제품 가격을 생각해봐도 1년 전처럼 부담스럽지 않게 가격이 내려왔으니까, 주변 동료들이 구입한다고 하면... "추천!!!" 한다~~

반응형

`kubectl apply` 명령을 통해서 container image를 pull하려면, Docker 회사의 container image pulling에 대한 rate limit 정책 때문에 docker.io 또는 docker.com에서 image를 pulling하지 못하는 경우가 있다.

그래서 일반적으로 docker.com에 회원 가입하고, 무제한으로 image pulling 할 수 있도록 유료 서비스도 이용하는데, `docker login ....` 그리고 `docker image pull ...`하는 것은 잘 되지만, kubectl 명령으로 pod의 container image를 pulling하려면 rate limit제약이 있다. 바로 해당 Pod의 'imagePullSecret' 정보가 없이  때문이다. Pod가 생성될 때, Pod의 manifest에 있는 imagePullSecret 정보를 보고, Container Image Registry에 인증 요청을 하는 것인데 대부분 Pod manifest에 이 imagePullSecret 정보를 설정하지 않는다. (일부러 안 하는 것은 아니고, 이런 것이 있는 줄 모르니까 사용자들이 실수를 한다)

 

가장 흔한 Use Case가 namespace 마다 Docker Registry에 접속할 수 있는 secret을 등록하고, Pod manifest에 이 secret을 참조하도록 하는 것이다.  그런데 이것도 실제 사용하려면 문제가 있다. 내가 직접 작성한 Pod manifest라면, imagePullSecret을 추가하면 끝날 일이지만 istio처럼 istioctl 이라는 명령이 내부적으로 pod manifest를 생성해서 pod 생성을 시도하는 경우라면, imagePullSecret을 내 마음대로 추가하기 어렵다.

 

그래서~  나는 어떻게 이 문제를 풀어야 하지? ㅠㅠ

 

 

우선 즉흥적으로 생각나는 대로 메모를 해보면,  (깊게 생각한다고 풀릴 문제는 아닌 듯...)

  • Cluster 전체에서 공용으로 imagePullSecret을 사용할 수 있도록 설정하는 기능이 있는지 확인한다.
  • ServiceAccount에 Secret을 등록하고, 항상 이 ServiceAccount로 생성되는 Pod에 imagePullSecret을 사용할 수 있도록 한다.
  • istioctl 같은 별개의 deployment 관리 명령이 있는 경우라면, istioctl option 중에서 imagePullSecret을 설정하는 parameter가 있는지 찾아본다.

Web Docs를 잘 찾아보니까, 고맙게도 kubernetes.io의 Web Docs에 내가 고민하고 있는 내용을 잘 설명해주고 있다.

https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry

 

Images

A container image represents binary data that encapsulates an application and all its software dependencies. Container images are executable software bundles that can run standalone and that make very well defined assumptions about their runtime environmen

kubernetes.io

 

 

위 Web Docs에서는 아래의 4 가지 답안을 줬는데, 가장 사람 손을 덜 타는 (A) 방법으로 문제를 풀어봤다.

 

(A) Configuring Nodes to Authenticate to a Private Registry      ## 내가 선택한 해결 방법
    - all pods can read any configured private registries
    - requires node configuration by cluster administrator

(B) Pre-pulled Images
    - all pods can use any images cached on a node
    - requires root access to all nodes to setup

(C) Specifying ImagePullSecrets on a Pod
    - only pods which provide own keys can access the private registry

(D) Vendor-specific or local extensions
    - if you're using a custom node configuration,
       you (or your cloud provider) can implement your mechanism for authenticating the node to the container registry.

 

 

그래서 위 (A) 방법대로 문제를 해결했다~  자세한 절차는 아래 Web Docs에 있다.  단순하게 따라하면 잘 동작한다.


https://kubernetes.io/docs/concepts/containers/images/#configuring-nodes-to-authenticate-to-a-private-registry

위 원문을 조금 요약해보자면 아래와 같다.

##
## If you run Docker on your nodes, 
## you can configure the Docker container runtime to authenticate to a private container registry.
## This approach is suitable if you can control node configuration.

## Here are the recommended steps to configuring your nodes to use a private registry. 
## In this example, run these on your desktop/laptop PC:

##   1. Run docker login "docker.io" for each set of credentials you want to use. 
##      This updates $HOME/.docker/config.json on your PC.
##   2. View $HOME/.docker/config.json in an editor to ensure it contains only the credentials you want to use.
##   3. Get a list of your nodes; for example:
##     - if you want the names: 
##       nodes=$( kubectl get nodes -o jsonpath='{range.items[*].metadata}{.name} {end}' )
##     - if you want to get the IP addresses: 
##       nodes=$( kubectl get nodes -o jsonpath='{range .items[*].status.addresses[?(@.type=="ExternalIP")]}{.address} {end}' )
##   4. Copy your local .docker/config.json to one of the search paths list above.
##     - for example, to test this out: 
##       for n in $nodes; do scp ~/.docker/config.json root@"$n":/var/lib/kubelet/config.json; done
##

위 설명대로 /root/.docker/config.json 파일을 모든 master node, worker node의 /var/lib/kubelet/config.json 경로에 복사한 후에 아래와 같이 Pod를 deploy하면서  docker.io에서 rate limit이 걸리지 않고 container image를 pulling 할 수 있는지 확인한다. 

 

$  kubectl  apply -f -  <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: busybox-sleep
spec:
  containers:
  - name: busybox
    image: busybox
    imagePullPolicy: Always
    args:
    - sleep
    - "1000000"
EOF

pod/busybox-sleep created

$ kubectl get pod
NAME            READY   STATUS    RESTARTS   AGE
busybox-sleep   1/1     Running   0          7s
$

 

주의:
나는 docker.io 유료 가입자이다.  이 글을 읽는 분도 본인이 사용하는 kubernetes cluster가 container image pulling rate limit 없이 동작하게 하려면 꼭 docker.io에 유료 가입해야 한다)

 

 

참고:

가장 일반적인 Use Case는 아래와 같이 namespace에 secret을 생성하고, 이 regcred 라는 secret을 Pod manifest에서 참조하도록 한다. 그런데, 모든 Pod의 manifest를 수정해야 하는 노동력이 추가되므로 이 방법을 권장하지 않는다.

(아마 imagePullSecret 정보를 자동으로 patch해주는 Mutating Admission Controller / WebHook Server를 만들어서 나의 Kubernetes Cluster에 추가해주면 사람이 개입할 일은 없겠지만, Webhook Server를 작성하는 것도 일인지라... ㅠㅠ)

 

$  kubectl create  -n ${MY-NS}  secret docker-registry  regcred  --docker-server=docker.io  --docker-username=${MYID}  --docker-password=${MYPW}

 

 

참고:

이 container image pulling rate limit 해결 방법은 순정 kubernetes cluster에서만 유효하고, Red Hat Openshift(OCP)는 Docker Registry 인증 문제를 해결하는 방식이 다르다. (결론만 말하자면, Red Hat OCP 쪽이 더 명령이 간단하고 쉽게 해결된다)

Red Hat에 비용을 내고 기술 지원 서비스를 받으면, Red Hat 직원이 알아서 해결해줬기 때문에 굳이 남의 밥벌이 영역의 일을 이렇게 공개된 글에 올리는 것은 예의가 아닌 것 같아서, Red Hat OCP에서의 Docker Registry 인증 문제에 대한 해결 방법은 언급하진 않겠다.

 

반응형

 

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

 

+ Recent posts