-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.json
More file actions
1 lines (1 loc) · 209 KB
/
Copy pathindex.json
File metadata and controls
1 lines (1 loc) · 209 KB
1
[{"uri":"http://kimmj.github.io/jenkins/install/","title":"Jenkins Install","tags":["jenkins","install"],"description":"","content":"Configure docker-compose.yaml 다음과 같이 docker-compose.yaml 파일을 적절한 디렉토리에 생성합니다.\nversion: \u0026#39;2\u0026#39; services: jenkins: image: \u0026#39;jenkins/jenkins:lts\u0026#39; ports: - \u0026#39;38080:8080\u0026#39; - \u0026#39;38443:8443\u0026#39; - \u0026#39;50000:50000\u0026#39; volumes: - \u0026#39;jenkins_data:/var/jenkins_home\u0026#39; volumes: jenkins_data: driver: local driver_opts: type: none device: $PWD/jenkins_data o: bind jenkins_data는 jenkins가 사용할 데이터들입니다. 이를 local에 폴더로 만들어줍니다.\nmkdir jenkins_data Start Jenkins 이제 docker-compose 명령어를 통해 실행합니다.\nsudo docker-compose up -d http://$IP:38080 으로 접속할 수 있습니다. 로컬에 설치하셨다면 http://localhost:38080으로 접속하면 됩니다.\nJenkins 세팅 처음에 다음과 같은 화면을 볼 수 있습니다.\n여기서 /var/jenkins_home/secrets/initialAdminPassword의 내용을 아래 칸에 입려하라고 지시합니다.\n저희는 앞서 $PWD/jenkins_data를 /var/jenkins_home에 binding했으므로, $PWD/jenkins_data/secrets/initialAdminPassword를 확인하면 됩니다. 또는 docker container에 직접 접속해서 해당 path로 이동 후 값을 확인해도 됩니다.\n이제 다음과 같은 화면이 뜰 것입니다.\n여기서는 왼쪽의 Install suggested plugins를 선택해줍니다.\n다음과 같이 설치가 진행됩니다.\n위의 plugin 설치과정이 완료되고 나면 다음과 같은 admin 계정 생성에 관한 화면이 나옵니다.\n알맞게 입력을 한 뒤 저장하면 접속 URL을 작성하라고 합니다. default로 적어져 있으니 확인후 변경이 필요하면 바꾸면 됩니다.\n여기까지 하면 기본 설정은 다 끝났습니다. Jenkins가 준비될 때까지 잠시 기다리면 home 화면이 뜹니다.\nReference https://hub.docker.com/_/jenkins/ "},{"uri":"http://kimmj.github.io/prometheus/install/","title":"Install Prometheus","tags":["install","prometheus"],"description":"","content":"Prometheus Prometheus는 opensource monitoring system입니다. 음악을 하는 사람들이 많이 이용하는 사이트인 SoundCloud에서 개발된 오픈소스입니다.\nPromQL이라는 Query문을 사용하여 metric을 수집할 수 있습니다. 자세한 내용은 나중에 따로 포스트를 작성하도록 하겠습니다.\n이 문서에서는 docker-compose를 통해 간단하게 prometheus를 설치해볼 것입니다. 또한 prometheus와 뗄레야 뗄 수 없는 단짝 Grafana도 함께 설치할 것입니다.\nPrometheus의 설치 제가 docker-compose를 선호하는 이유는 너무나도 간단하게, dependency가 있는 어플리케이션을 설치할 수 있기 때문입니다. 아래에 예시에서도 잘 드러나있습니다.\n저는 monitoring/ 폴더 아래에 docker-compose.yaml이라는 이름으로 파일을 생성했습니다.\nversion: \u0026#39;3\u0026#39; networks: back-tier: services: prometheus: image: prom/prometheus restart: unless-stopped volumes: - ./config/prometheus.yml:/etc/prometheus/prometheus.yml ports: - \u0026#34;9090:9090\u0026#34; networks: - back-tier grafana: image: grafana/grafana:6.5.3 ports: - 9080:3000 user: \u0026#34;0\u0026#34; # $(id -u) https://community.grafana.com/t/new-docker-install-with-persistent-storage-permission-problem/10896/5 depends_on: - prometheus volumes: - ./grafana/provisioning/:/etc/grafana/provisioning/ - ./grafana_data:/var/lib/grafana networks: - back-tier 위의 예시에서 services.grafana.user=\u0026quot;0\u0026quot;으로 설정이 되어있는 것이 보이실 것입니다. 이 부분을 $(id -u)의 결과값으로 변경하시면 됩니다. 관련된 내용은 옆에 주석에서도 확인이 가능합니다.\n이렇게 하고난 뒤, 설치하는 방법은 너무나도 간단합니다.\ncd monitoring docker-compose up -d 잠시 내리고 싶을땐 다음과 같이 하면 됩니다.\ndocker-compose down 설정상, Prometheus는 9090 포트를 통해서 expose 되어있습니다. 또한 Grafana는 9080 포트를 사용하고 있습니다. Grafana의 기본 ID/PW는 admin/admin입니다.\n설정 Prometheus를 사용하기 위해서는 prometheus.yml이라는 파일을 관리해주어야 합니다. 이곳에서 어느 서비스의 metric을 가지고 올지 설정할 수 있습니다.\n해당 파일은 docker-compose.yaml을 참조해 보았을 때, monitoring/config/prometheus.yml을 수정하면 된다는 것을 알 수 있습니다. 수정 후에는 docker-compose down을 통해 잠시 내렸다가 docker-compose up -d를 통해 올리면 수정사항이 반영됩니다.\n"},{"uri":"http://kimmj.github.io/spinnaker/installation/","title":"Installation","tags":[],"description":"","content":"Spinnaker Installation spinnaker를 설치해 볼 것입니다.\n쉽지 않았던 여정들을 기록하려고 합니다.\n Overview Install Halyard Choose Cloud Providers Choose Your Environment Choose a Storage Service Deploy and Connect Install in Air Gaped Environment "},{"uri":"http://kimmj.github.io/spinnaker/installation/overview/","title":"Overview","tags":["install","spinnaker"],"description":"","content":"Overview of install Spinnaker 어떻게 Spinnaker를 설치 및 배포하는지 알아보도록 하겠습니다.\n가장 먼저 최소 사양을 확인해보도록 하겠습니다.\n링크 : https://www.spinnaker.io/guides/developer/getting-set-up/#system-requirements\n 램 18 GB CPU 4코어 Ubuntu 14.04, 16.04, 18.04 Spinnaker 자체가 클라우드 환경에만 배포가 가능하기 때문에, 아마도 \u0026ldquo;전체 클라우드를 합하여 저정도면 된다\u0026quot;를 의미하는 것 같습니다.\n설치 방법은 두가지로 나뉩니다.\n 테스트를 목적으로 Helm Chart를 통한 설치 실제로 사용할 목적으로 halyard를 통한 설치 저는 여기서 2번 halyard를 통한 설치를 해보려고 합니다.\n전체적인 프로세스를 먼저 설명드리자면 다음과 같습니다.\n halyard 설치 Cloud Provider(클라우드 제공자) 선택 배포 환경 선택 Storage Service 선택 배포 및 접속 config 백업하기 그리고 저는 다음과 같은 환경에서 테스트를 할 예정입니다.\n Cloud Provider: Kubernetes on-prem (4 VMs) 1 for master (4GB Mem, 1 CPU) 3 for worker (each 8GM Mem, 4 CPU) Environment: Distributed installation on Kubernetes Storage Service: Minio Deploy and Connect: expose by NodePort OS : Ubuntu 18.04.2 Server "},{"uri":"http://kimmj.github.io/","title":"Ibiza","tags":[],"description":"","content":"Ibiza CoreDNS CoreDNS를 통한 Dns Server 구축하기 with docker Harbor Private Docker Registry 오픈소스: Harbor란 Harbor 설치 Git git-secret을 통한 github 파일 암호화 Gitignore 설정 Python [번역]Python을 통해 이쁜 CLI 만들기 Docker http를 사용하는 docker registry를 위한 insecure registry 설정 Docker를 sudo없이 실행하기 [docker-compose] container에서 다른 container로 접속하기 Jenkins Jenkins Install Workspace@2를 변경하기 - Workspace List 설정 변경 IaC [번역] What Is Infrastructure as a Code? How It Works, Best Practices, Tutorials CICD Deploy Strategy CSS background image 어둡게 하기 Greater Than Sign Prometheus Install Prometheus Federation Kubernetes Concepts Controllers Overview Kubernetes Components Pods Kubernetes Service는 어떻게 iptables 설정이 되는가 Kubernetes에서의 cpu requests, cpu limits는 어떻게 적용될까 CKA: Certified Kubernetes Administrator 취득 후기 [번역] 쿠버네티스에서의 Port, TargetPort, NodePort Stern을 이용하여 여러 pod의 log를 한번에 확인하기 Hugo Ibiza Font Change Hugo에 Google Analytics 적용하기 Hugo에 Comment 추가하기 (Utterance) HUGO로 HTML이 되지 않을 때 가능하게 하는 방법 Git Action으로 hugo build 자동화 하기 Spinnaker Installation Overview Install Halyard Choose Cloud Providers Choose Your Environment Choose a Storage Service Deploy and Connect Install in Air Gaped Environment CanaryAnalysis Canary Analysis Tips Pipeline Expressions Ansible Create Vm With Ansible Libvirt Ubuntu Tools Tmux Network Netplan으로 static IP 할당받기 Linux에서 압축파일 분할하기 Samba를 통한 디스크 공유 Ubuntu의 Login Message 수정하기 reboot 후에 tmux를 실행시켜 원하는 작업을 하기 oh-my-zsh에서 home key와 end key가 안될 때 해결방법 Ubuntu에서 Base64로 인코딩, 디코딩하기 Editor(vi)가 없을 때 파일 수정하기 열려있는 포트 확인하기 pipe를 사용한 명령어를 watch로 확인하기 watch를 사용할 때 alias 이용하기 password 없이 ssh 접속하기 SSH Tunneling 사용법 Gateway를 이용하여 SSH 접속하기 Hostname 변경하기 추가 입력절차(prompt) 없이 Ubuntu 설치하는 이미지 만들기 Ubuntu 설치 시 Boot Parameter를 수정하기 sudo를 password 없이 사용하기 "},{"uri":"http://kimmj.github.io/spinnaker/installation/install-halyard/","title":"Install Halyard","tags":["spinnaker","install","halyard","proxy"],"description":"","content":"halyard란? halyard는 Spinnaker를 배포할 때 사용하는 CLI 툴입니다.\nhalyard는 Spinnaker 관련 설정들의 validation, 배포한 환경 백업, 설정 추가 및 변경에 사용됩니다.\n설치 방법 선택하기 총 2가지 방법으로 halyard를 설치할 수 있습니다.\n Debian/Ubuntu나 macOS에 직접 설치하기 Docker 사용하기 Spinnaker Docs에서는 실제 Production 환경이라면 직접 설치하는 방법을, 그게 아니라 간단하게 사용하려면 docker를 사용해도 된다고 하고 있습니다.\n그리고 한가지의 옵션이 더 있습니다.\n 인터넷이 되지 않는 환경 (프록시나 방화벽 등으로 halyard를 통한 설치가 어려운 경우) 이 글을 작성하고 있는 환경은 인터넷이 잘 되는 환경입니다. 그리고 두가지 모두 시도해 보도록 하겠습니다.\nDebian/Ubuntu나 macOS에 직접 설치하기 공식 Docs에서 halyard는 다음과 같은 환경에서 동작한다고 말하고 있습니다.\n Ubuntu 14.04, 16.04 or 18.04 (Ubuntu 16.04 requires Spinnaker 1.6.0 or later) Debian 8 or 9 macOS (tested on 10.13 High Sierra only) 이제 직접 설치를 시작해보도록 하겠습니다.\n시작하기 전에, halyard를 설치하기 위해서는 root 계정이 아닌 계정이 필요합니다. 만일 root만 있다면 spinnaker를 위한 계정을 생성해 줍니다.\nadduser spinnaker 위처럼 생성한 계정에 sudoers 권한을 줍니다.\nadduser spinnaker sudo 최신 버전의 halyard 다운로드 Debian/Ubuntu:\ncurl -O https://raw.githubusercontent.com/spinnaker/halyard/master/install/debian/InstallHalyard.sh macOS:\ncurl -O https://raw.githubusercontent.com/spinnaker/halyard/master/install/macos/InstallHalyard.sh 설치 sudo bash InstallHalyard.sh 확인 hal -v 추가사항 . ~/.bashrc를 실행하여 bash completion 활성화\n여기서 proxy 환경이라면 halyard의 jvm에 proxy 옵션을 추가해주어야 합니다.\nvi /opt/halyard/bin/halyard을 통해 halyard의 jvm 옵션을 추가할 수 있습니다.\nDEFAULT_JVM_OPTS=\u0026#39;\u0026#34;-Djava.security.egd=file:/dev/./urandom\u0026#34; \u0026#34;-Dspring.config.additional-location=/opt/spinnaker/config/\u0026#34; \u0026#34;-Dhttps.proxyHost=\u0026lt;proxyHost\u0026gt; -Dhttps.proxyPort=\u0026lt;proxyPort\u0026gt;\u0026#34; \u0026#34;-Dhttp.proxyHost=\u0026lt;proxyHost\u0026gt; -Dhttp.proxyPort=\u0026lt;proxyPort\u0026gt;\u0026#34;\u0026#39; 위의 설정에서 다음과 같이 proxy를 추가해줍니다. 그 다음 halyard를 재시동합니다.\nhal shutdown hal config 아랫줄의 hal config는 의도적으로 halyard를 구동시키기 위함입니다.\ndocker로 halyard 사용하기 다음의 명령어는 공식 docs에서 제공하는 명령어입니다.\ndocker run -p 8084:8084 -p 9000:9000 \\ --name halyard --rm \\ -v ~/.hal:/home/spinnaker/.hal \\ -d \\ gcr.io/spinnaker-marketplace/halyard:stable kubernetes로 배포하려 할 경우, kubectl 명령어에서 사용할 kubeconfig 파일이 필요합니다. 이 또한 -v 옵션으로 주어야 합니다. 그리고 그 kubeconfig 파일을 읽도록 설정해야 합니다.\ndocker run -p 8084:8084 -p 9000:9000 \\ --name halyard --rm \\ -v ~/.hal:/home/spinnaker/.hal \\ -v ~/.kube:/home/spinnaker/.kube \\ -e KUBECONFIG=/home/spinnaker/.kube/config \\ -d \\ gcr.io/spinnaker-marketplace/halyard:stable 사실 5번째 줄의 -e KUBECONFIG=/home/spinnaker/.kube/config은 없어도 default로 들어가있는 설정입니다. 하지만 혹시나 위에서 /home/spinnaker/.kube가 아닌 다른곳을 저장공간으로 둔다면 아래의 설정도 바뀌어야 합니다.\n프록시 환경이라면 다음과 같이 JAVA_OPT를 추가해주어야 합니다.\ndocker run -p 8084:8084 -p 9000:9000 \\ --name halyard --rm -d \\ -v ~/.hal:/home/spinnaker/.hal \\ -v ~/.kube:/home/spinnaker/.kube \\ -e http_proxy=http://\u0026lt;proxy_host\u0026gt;:\u0026lt;proxy_port\u0026gt; \\ -e https_proxy=https://\u0026lt;proxy_host\u0026gt;:\u0026lt;proxy_port\u0026gt; \\ -e JAVA_OPTS=\u0026#34;-Dhttps.proxyHost=\u0026lt;proxy_host\u0026gt; -Dhttps.proxyPort=\u0026lt;proxy_port\u0026gt;\u0026#34; \\ -e KUBECONFIG=/home/spinnaker/.kube/config \\ gcr.io/spinnaker-marketplace/halyard:stable 그래도 안된다면.. 아마도 관리가 엄격한 네트워크를 사용하고 계실 것이라고 예상됩니다. 저 또한 그랬으니까요.\nhalyard는 설정 및 버전등의 정보를 bucket (Google Cloud Storage)로 관리한다고 합니다. 따라서 이곳으로 연결이 되지 않는다면 validation, version list 등의 상황에서 timeout이 날 것입니다.\nSpinnaker에서는 이를 확인하기 위해 gsutil을 사용하여 bucket의 주소인 gs://halconfig에 연결할 수 있는지 확인해보라고 합니다.\n또는 curl을 이용해서도 확인이 가능합니다.\n먼저 gsutil은 google storage 서비스에 접속하는 CLI 툴입니다. Docs에서 설치방법을 확인하여 설치할 수 있습니다.\n설치가 완료되었다면 gsutil로 접속이 가능한지부터 확인합니다.\ngsutil ls gs://halconfig 두번째로 curl을 사용하는 방법입니다.\ncurl storage.googleapis.com/halconfig 결과물들이 나온다면 정상적으로 bucket에는 접속이 가능한 것입니다. hal config 명령어가 성공하지 않지만 bucket에 접속이 가능한 케이스는 아직 보지 못했습니다.\n우선 여기까지 해서 bucket에 접속이 불가능하다고 판단이 되면, 인터넷이 없는 환경에서 설치하는 방법을 고려해보아야 합니다. 또는 googleapis.com url로 proxy에서 사이트가 차단되었는지 확인하고, SSL도 해제할 경우 해결될 수도 있습니다.\n"},{"uri":"http://kimmj.github.io/spinnaker/installation/choose-cloud-providers/","title":"Choose Cloud Providers","tags":["install","spinnaker"],"description":"","content":"Spinnaker를 배포할 환경을 설정해 주어야 합니다. 여기에서는 제가 구축한 local kubernetes cluster를 사용할 것입니다.\n먼저 2가지가 필요합니다.\n kubeconfig 파일 kubeconfig 파일은 일반적으로 ~$HOME/.kube/config 파일을 의미합니다. 저는 local kubernetes cluster로 이동하여 해당 파일을 halyard를 위한 vm으로 복사하였습니다. kubectl CLI 툴 이제 hal config 명령어를 통해 kubernetes cluster를 추가합니다.\nhal config provider kubernetes enable CONTEXT=$(kubectl config current-context) hal config provider kubernetes account add wonderland \\ --provider-version v2 \\ --context $CONTEXT hal config features edit --artifacts true "},{"uri":"http://kimmj.github.io/spinnaker/installation/choose-your-environment/","title":"Choose Your Environment","tags":["install","spinnaker"],"description":"","content":"Spinnaker를 배포하는 방법에는 3가지가 있습니다. Kubernetes 환경에 배포하기, local debian으로 배포하기, local git으로 배포하기가 있습니다.\n여기에서는 Kubernetes 환경에 배포하기를 진행할 것입니다.\nACCOUNT=wonderland hal config deploy edit --type distributed --account-name $ACCOUNT 위와같이 설정하면 됩니다. ACCOUNT는 kubernetes cluster를 추가할 때 사용했던 이름을 사용하면 됩니다.\n"},{"uri":"http://kimmj.github.io/coredns/","title":"CoreDNS","tags":[],"description":"","content":"CoreDNS CoreDNS를 통한 Dns Server 구축하기 with docker "},{"uri":"http://kimmj.github.io/harbor/","title":"Harbor","tags":[],"description":"","content":"Harbor Private Docker Registry 오픈소스: Harbor란 Harbor 설치 "},{"uri":"http://kimmj.github.io/git/","title":"Git","tags":[],"description":"","content":"Git git-secret을 통한 github 파일 암호화 Gitignore 설정 "},{"uri":"http://kimmj.github.io/python/","title":"Python","tags":[],"description":"","content":"Python [번역]Python을 통해 이쁜 CLI 만들기 "},{"uri":"http://kimmj.github.io/docker/","title":"Docker","tags":[],"description":"","content":"Docker http를 사용하는 docker registry를 위한 insecure registry 설정 Docker를 sudo없이 실행하기 [docker-compose] container에서 다른 container로 접속하기 "},{"uri":"http://kimmj.github.io/jenkins/","title":"Jenkins","tags":[],"description":"","content":"Jenkins Jenkins Install Workspace@2를 변경하기 - Workspace List 설정 변경 "},{"uri":"http://kimmj.github.io/iac/","title":"IaC","tags":[],"description":"","content":"Infrastructure as Code [번역] What Is Infrastructure as a Code? How It Works, Best Practices, Tutorials "},{"uri":"http://kimmj.github.io/cicd/","title":"CICD","tags":[],"description":"","content":"CICD Deploy Strategy "},{"uri":"http://kimmj.github.io/css/","title":"CSS","tags":[],"description":"","content":"CSS background image 어둡게 하기 Greater Than Sign "},{"uri":"http://kimmj.github.io/kubernetes/concepts/controllers-overview/","title":"Controllers Overview","tags":["kubernetes","concepts"],"description":"","content":"Contents 이 포스트에서는 Kubernetes의 Controller들에 대해서 알아보도록 하겠습니다. 가장 작은 단위인 Container부터, 상위 개념인 Deployment, StatefulSet까지 다루어 보도록 하겠습니다.\n Containers Pods ReplicaSets Deployments StatefulSets Monolithic vs. Microservice 우선 Monolithic과 Microservice에 대해서 짚고 넘어가도록 하겠습니다.\nMonolithic의 개념은 하나의 큰 어플리케이션을 말합니다. 여러 사람이 개발을 하고 나서 하나의 큰 패키지로 빌드하고 이를 배포하죠. 간단한 서비스라면 문제가 발생하지는 않겠지만, 점점 코드의 수가 늘어나고 거대해질 수록 문제점이 생깁니다. 예를 들면 빌드시간이 오래걸린다던지, scale-out을 하기 힘들다던지 하는 문제가 있겠네요.\n반면 Microservice는 하나의 큰 어플리케이션을 여러 조각으로 쪼갠 것을 의미합니다. 각 조각(microservice)는 자신의 역할이 있고, 이를 잘 수행하면 됩니다. 전체적인 관점에서 보면 어플리케이션은 microservice들간의 통신으로 행해진다고 보면 될 것 같습니다.\nMonolithic에 비해 Microservice는 몇가지 장점들이 있습니다.\n scale-out이 용이합니다.\nMonolithic에 비해 microservice의 단위는 작기 때문에 scale-out 하는데 시간도 오래 걸리지 않고, 간단합니다. 빠른 배포가 가능합니다.\nMonolithic에서는 사이즈가 커질수록 빌드하는 시간이 점점 길어진다고 설명했었습니다. 이는 곧 요즘처럼 트렌드라던지 상황이 급변하는 상황에서 약점이 될 수 밖에 없습니다. 반면 Microservice는 작은 조각이기 때문에 빌드하는 시간이 Monolithic에 비해 뛰어날 수밖에 없습니다. 그리고 이를 배포하는 시간도 매우 줄어들게 되죠. 문제가 발생하였을 때 영향이 적습니다.\n쉽게 우리가 가장 잘 알고있는 게임중 하나인 LoL을 가지고 설명해 보도록 하겠습니다. 만약 LoL을 플레이하고 싶은데, 상점에 에러가 있어서 이용하지 못한다면 어떻게 되나요? LoL이 Monolithic이었다면 게임 플레이가 막혀서 엄청난 원성을 샀을 것입니다. 하지만 LoL또한 Microservice이기 때문에 문제가 발생한 곳만 이용하지 못할 뿐, 나머지 서비스는 정상적으로 이용이 가능합니다. 따라서 복구하기도, 운영하기도 훨씬 쉽습니다. 이렇게 보면 무조건 Microservice만 해야하는 것처럼 보이기도 합니다. 하지만 세상일이 모두 그렇듯 여기에도 정답은 없습니다. 자신이 개발하고자 하는 어플리케이션의 특성을 잘 파악해서 한가지를 선택하고 개발하는 것이 좋은 방향이 될 것 같습니다.\nConatiners 이제 본격적인 설명으로 넘어가보도록 하겠습니다.\nDocker를 사용해 보았다면 container도 친숙한 개념이 될 것 같습니다. container는 VirtualBox처럼 가상화를 하지만, OS까지 가상화하는 것이 아니라 host OS 위에서 커널을 공유하는 방식으로 가상화를 합니다. 어려운 말일수도 있지만 간단하게 말하자면 VirtualBox같은 hypervisor에 의한 가상화보다 훨씬 가벼운 방법으로 가상화를 할 수 있다고 생각하시면 됩니다. 여기서 가볍다는 의미는 빠르게 생성/삭제할 수 있고 용량도 작다는 의미입니다.\nKubernetes는 이런 Docker와 같은 container runtime 기반으로 동작합니다. 그리고 각 container는 어플리케이션 내에서 자신의 역할을 수행하는 것들입니다. Docker를 통해 컨테이너를 동작시키는 것처럼, Kubernetes도 컨테이너를 Docker같은 container runtime의 힘을 빌려 동작시킵니다.\nPods Pod는 Kubernetes에서 어플리케이션을 관리하는 단위입니다.\n하나의 Pod는 여러개의 Container로 구성될 수 있습니다. 즉, 너무나 밀접하게 동작하고 있는 여러 Container를 하나의 Pod로 묶어 함께 관리하는 것입니다.\n이 때, 하나의 Pod 내에 존재하는 모든 Container들은 서로 localhost를 통해 통신할 수 있습니다.\nKubernetes에서 Pod는 언제 죽어도 이상하지 않은 것으로 취급됩니다. 동시에 언제 생성되어도 이상하지 않은 것, 어디에 떠있어도 이상하지 않은 것이죠. 그 만큼 어플리케이션 개발자는 Pod를 구성할 때 하나의 Pod가 장애가 나는 경우에도 어플리케이션이 제대로 동작할 수 있도록 만들어야 합니다. 언제 없어졌다가 생성될지 모르니까요.\nReplicaSet ReplicaSet은 Pod를 생성해주는 녀석입니다.\n단일 Pod를 그냥 생성해도 되지만 그렇게 할 경우 Kubernetes가 주는 여러 이점들, 예를 들어 auto healing, auto scaling, rolling update 등을 이용하지 못합니다. 때문에 Pod를 관리해주는 Controller가 필요하게 되는데 이것이 ReplicaSet입니다.\nReplicaSet은 자신이 관리해야하는 Pod의 template을 가지고 있습니다. 그리고 주기적으로 Kubernetes를 주시하며 내가 가지고 있는 template에 대한 Pod가 원하는 숫자만큼 잘 있는지 확인합니다. 부족하다면 Pod를 더 생성하고 너무 많으면 Pod를 삭제합니다.\nReplicaSet은 Pod를 label을 기준으로 관리합니다. 자신이 가지고 있는 matchLabels와 일치하는 Pod들이 자신과 관련된 Pod라고 인식하는 것이죠. 따라서 label을 운영중에 바꾸는 일은 웬만해선 피해야 합니다. 고아가 발생하여 어느 누구도 관리해주지 않는 Pod가 남게 될 수 있기 때문입니다.\n또한 ReplicaSet은 자신이 생성한 파드들을 \u0026lt;ReplicaSet의 이름\u0026gt;-\u0026lt;hash 값\u0026gt;으로 생성합니다. 따라서 Pod의 이름만 보고도 어떤 ReplicaSet이 생성했는지 알 수 있게되죠.\nDeployments 위에서는 ReplicaSet에 대해서 이야기해 보았습니다. Deployments는 ReplicaSet보다 상위 개념의 Controller입니다.\n만약 Pod의 template을 수정하는 경우가 생긴다면 어떻게 해야 할까요? 예를 들어 cpu 할당량을 바꾼다던지, image를 다른 이미지로 변경한다던지 하는 경우가 발생한다면요. ReplicaSet만 존재한다면 이런 상황에서 새로운 template을 가지고 ReplicaSet을 생성하고, 기존의 ReplicaSet을 삭제하거나 replicas: 0으로 변경하여 ReplicaSet만 남아있고 실제 Pod는 없도록 해야합니다.\n그렇다면 새로 만든 template에 오류가 있어서 이전 버전으로 돌아가고 싶다면 어떻게 해야할까요? 이전에 작성했던 ReplicaSet의 replicas를 늘리고, 현재 올라가있던 ReplicaSet의 replicas를 줄이면 될 것 입니다. 하지만 이는 일일이 기억해야하는 크나큰 단점이 있겠죠.\n때문에 ReplicaSet을 관리해주는 Deployment가 필요합니다. Deployment는 ReplicaSet을 생성하고 이 ReplicaSet이 Pods를 생성하도록 만듭니다. 그리고 자신이 관리하는 ReplicaSet의 labels를 자신의 matchLabels로 일치시켜 구분합니다.\n그렇다면 Pod의 template이 변경되는 상황엔 이번에는 어떻게 적용이 될까요?\nDeployment는 새로운 ReplicaSet을 생성하고, 기존 ReplicaSet의 replicas를 0으로 줄입니다. 그러면서 자신이 생성했던 ReplicaSet들을 revision으로 관리하죠. 이렇게 되면 사용자는 kubectl 명령어만 가지고 이전 template을 가진 Pod로 변경할 수 있습니다.\n또한 Deployments가 ReplicaSet을 생성할 때는 \u0026lt;Deployment의 이름\u0026gt;-\u0026lt;hash 값\u0026gt;으로 생성합니다. 위에서 ReplicaSet도 Pod를 생성할 때 \u0026lt;ReplicaSet의 이름\u0026gt;-\u0026lt;hash 값\u0026gt;이라고 설명했었습니다. 따라서 Deployments에 의해 만들어진 Pod들은 \u0026lt;Deployment의 이름\u0026gt;-\u0026lt;ReplicaSet에 대한 hash 값\u0026gt;-\u0026lt;Pod에 대한 hash 값\u0026gt;과 같은 형태를 띄게 됩니다.\nStatefulSets StatefulSet은 좀 특이한 녀석입니다.\nPod는 어디에 떠있어도 이상하지 않은 것이라고 언급했었습니다. 그런데 StatefulSet은 그렇지 않습니다. 이름에 걸맞게 이전의 상태를 그대로 보존하고 있어야 합니다. 따라서 여러 제약사항들이 생기기도 합니다.\n여기에서는 Pod와의 관계만 알아보고 넘어가도록 하겠습니다.\nStatefulSets는 Deployments와는 다르게 ReplicaSet을 생성하지 않습니다. 대신 자신이 직접 Pod를 생성합니다. 이 때 Pod의 label 속성을 자신의 matchLabels과 일치시켜 자신이 관리하고 있는 Pod를 구분합니다.\n또한 이름도 hash값을 사용하지 않고 0부터 시작하는 숫자를 사용합니다. 즉, \u0026lt;StatefulSets의 이름\u0026gt;-\u0026lt;0부터 오름차순\u0026gt;의 이름을 가지는 Pod를 생성합니다.\n만약 생성해야하는 Pod의 template에 변화가 있다면 어떻게 해야할까요?\nStatefulSets는 Deployments와 다르게 변경할 수 있는 부분에 제약이 있습니다. 이 경우 절대 StatefulSet을 update할 수 없습니다. 따라서 기존 StatefulSet을 삭제하고 다시 kubectl apply와 같은 명령어로 새로운 template을 가지고 생성해야합니다.\nRollback에도 제약사항이 있습니다.\nRollback시 StatefulSet은 Pod가 완전히 Ready상태가 되길 기다립니다. 그런데 만약 StatefulSet이 생성한 Pod가 ImagePullbackOff같은 에러에 빠지게 된다면, 또는 영원히 rediness probe에 의해 Ready 상태가 되지 않는다면 StatefulSet의 rollback은 멈춰버립니다. 이는 Known Issue로 수동으로 해당 Pod를 삭제하는 방법밖에 없습니다.\n따라서 웬만하면 설계를 할 때 StatefulSet을 지양하는 것이 Kubernetes의 Design에 더욱 맞는 방향일 것입니다.\n마치며.. 이렇게 Container, Pods, ReplicaSets, Deployments, StatefulSets의 관계를 중심으로 알아보는 시간을 가졌습니다. 혹시나 잘못된 부분이 있다면 언제든지 댓글 또는 메일로 알려주시면 감사하겠습니다.\n"},{"uri":"http://kimmj.github.io/kubernetes/concepts/","title":"Concepts","tags":[],"description":"","content":"Kubernetes Concepts Controllers Overview Kubernetes Components Pods "},{"uri":"http://kimmj.github.io/ubuntu/tools/","title":"Tools","tags":[],"description":"","content":"Ubuntu Tools Tmux "},{"uri":"http://kimmj.github.io/prometheus/","title":"Prometheus","tags":[],"description":"","content":"Prometheus Install Prometheus Federation "},{"uri":"http://kimmj.github.io/spinnaker/canaryanalysis/","title":"CanaryAnalysis","tags":[],"description":"","content":"Spinnaker Canary Analysis Canary Analysis는 Spinnaker에서 자동으로 분석을 통해 새로운 버전에 문제가 없는지 확인해주는 pipeline입니다.\n Canary Analysis "},{"uri":"http://kimmj.github.io/spinnaker/installation/choose-a-storage-service/","title":"Choose a Storage Service","tags":["install","spinnaker","minio"],"description":"","content":"Spinnaker들의 데이터를 저장할 공간입니다.\n여러가지 옵션들이 있지만, 저는 local로 운용할 수 있는 minio를 통해 데이터를 저장해 볼 것입니다.\nminio를 docker-compose를 통해 쉽게 배포하도록 할 것입니다. 먼저, docker-compose를 설치합니다.\nsudo curl -L \u0026#34;https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)\u0026#34; -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose 그 뒤 minio의 docker-compose.yaml을 만듭니다.\nversion: '3.7' services: minio: image: minio/minio:RELEASE.2020-01-16T22-40-29Z volumes: - ./data:/data ports: - \u0026quot;9000:9000\u0026quot; environment: MINIO_ACCESS_KEY: minio MINIO_SECRET_KEY: minio123 command: server /data healthcheck: test: [\u0026quot;CMD\u0026quot;, \u0026quot;curl\u0026quot;, \u0026quot;-f\u0026quot;, \u0026quot;http://localhost:9000/minio/health/live\u0026quot;] interval: 30s timeout: 20s retries: 3 docker-compose를 통해서 deamon으로 실행합니다.\ndocker-compose up -d 이제 halyard와 연동을 하도록 합니다.\n먼저, ~/.hal/default/profiles/front50-local.yml 파일을 다음과 같이 생성합니다.\nspinnaker: s3: versioning: false 그 다음 다음의 명령어로 연동을 합니다.\nENDPOINT=http://10.0.2.4:9000 MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 echo $MINIO_SECRET_KEY | hal config storage s3 edit --endpoint $ENDPOINT \\ --access-key-id $MINIO_ACCESS_KEY \\ --secret-access-key hal config storage edit --type s3 "},{"uri":"http://kimmj.github.io/kubernetes/","title":"Kubernetes","tags":[],"description":"","content":"Kubernetes Kubernetes는 deploy, scaling, 그리고 컨테이너화된 애플리케이션의\nmanagement를 자동화 해주는 open source container orchestration engine입니다.\n Concepts Controllers Overview Kubernetes Components Pods Kubernetes Service는 어떻게 iptables 설정이 되는가 Kubernetes에서의 cpu requests, cpu limits는 어떻게 적용될까 CKA: Certified Kubernetes Administrator 취득 후기 [번역] 쿠버네티스에서의 Port, TargetPort, NodePort Stern을 이용하여 여러 pod의 log를 한번에 확인하기 "},{"uri":"http://kimmj.github.io/hugo/ibiza/","title":"Ibiza","tags":[],"description":"","content":"Hugo Ibiza Ibiza는 이 블로그를 만드는 프로젝트입니다.\n Font Change "},{"uri":"http://kimmj.github.io/ubuntu/network/","title":"Network","tags":[],"description":"","content":"Ubuntu Network Netplan으로 static IP 할당받기 "},{"uri":"http://kimmj.github.io/hugo/","title":"Hugo","tags":[],"description":"","content":"Hugo fast static website engine\n Ibiza Font Change Hugo에 Google Analytics 적용하기 Hugo에 Comment 추가하기 (Utterance) HUGO로 HTML이 되지 않을 때 가능하게 하는 방법 Git Action으로 hugo build 자동화 하기 "},{"uri":"http://kimmj.github.io/spinnaker/","title":"Spinnaker","tags":[],"description":"","content":"CI/CD Spinnaker Spinnaker는 Kubernetes 환경에서 배포 자동화를 위해 만들어진 툴입니다.\n배포하려는 클러스터가 GKE인지, EKS인지, On-Premise 환경인지 상관없이 하나의 툴로 배포하기 위해 만들어졌습니다.\n이 툴 자체가 MSA 구조로 만들어져있습니다.\n Installation Overview Install Halyard Choose Cloud Providers Choose Your Environment Choose a Storage Service Deploy and Connect Install in Air Gaped Environment CanaryAnalysis Canary Analysis Tips Pipeline Expressions "},{"uri":"http://kimmj.github.io/ansible/","title":"Ansible","tags":[],"description":"","content":"Ansible Create Vm With Ansible Libvirt "},{"uri":"http://kimmj.github.io/ubuntu/","title":"Ubuntu","tags":[],"description":"","content":"Ubuntu Ubuntu에서 배운 것들을 기록하는 공간입니다.\n Tools Tmux Network Netplan으로 static IP 할당받기 Linux에서 압축파일 분할하기 Samba를 통한 디스크 공유 Ubuntu의 Login Message 수정하기 reboot 후에 tmux를 실행시켜 원하는 작업을 하기 oh-my-zsh에서 home key와 end key가 안될 때 해결방법 Ubuntu에서 Base64로 인코딩, 디코딩하기 Editor(vi)가 없을 때 파일 수정하기 열려있는 포트 확인하기 pipe를 사용한 명령어를 watch로 확인하기 watch를 사용할 때 alias 이용하기 password 없이 ssh 접속하기 SSH Tunneling 사용법 Gateway를 이용하여 SSH 접속하기 Hostname 변경하기 추가 입력절차(prompt) 없이 Ubuntu 설치하는 이미지 만들기 Ubuntu 설치 시 Boot Parameter를 수정하기 sudo를 password 없이 사용하기 "},{"uri":"http://kimmj.github.io/spinnaker/installation/deploy-and-connect/","title":"Deploy and Connect","tags":["spinnaker","install"],"description":"","content":"드디어 마지막 절차입니다.\n먼저 어떤 버전을 설치할지 확인후 설정합니다.\nhal version list 작성 기준으로 최신 버전이 1.17.6이므로 이를 설정합니다.\nhal config version edit --version 1.17.6 halyard를 NodePort로 노출시키기 위해 api와 ui에 base url을 부여합니다.\nhal config security ui edit --override-base-url http://192.168.8.22:30100 hal config security api edit --override-base-url http://192.168.8.22:30200 이제 본격적으로 deploy를 하도록 합니다.\nhal deploy apply 그 후 Spinnaker를 NodePort로 서비스합니다.\nkubectl patch svc spin-deck -n spinnaker --type=\u0026#39;json\u0026#39; -p \u0026#39;[{\u0026#34;op\u0026#34;:\u0026#34;replace\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/spec/type\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;NodePort\u0026#34;}]\u0026#39; kubectl patch svc spin-gate -n spinnaker --type=\u0026#39;json\u0026#39; -p \u0026#39;[{\u0026#34;op\u0026#34;:\u0026#34;replace\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/spec/type\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;NodePort\u0026#34;}]\u0026#39; kubectl patch svc spin-deck -n spinnaker --type=\u0026#39;json\u0026#39; -p \u0026#39;[{\u0026#34;op\u0026#34;:\u0026#34;replace\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/spec/ports/0/nodePort\u0026#34;,\u0026#34;value\u0026#34;: 30100}]\u0026#39; kubectl patch svc spin-gate -n spinnaker --type=\u0026#39;json\u0026#39; -p \u0026#39;[{\u0026#34;op\u0026#34;:\u0026#34;replace\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/spec/ports/0/nodePort\u0026#34;,\u0026#34;value\u0026#34;: 30200}]\u0026#39; 이제 Spinnaker로 접속하여 확인합니다. url은 http://:30100 입니다.\n여기까지 했으면 Spinnaker를 Kubernetes에서 사용할 수 있습니다.\n"},{"uri":"http://kimmj.github.io/spinnaker/installation/install-in-air-gaped-environment/","title":"Install in Air Gaped Environment","tags":["install","spinnaker","air-gaped"],"description":"","content":"이번에는 인터넷이 되지 않는 환경에서 어떻게 Spinnaker를 설치하는지에 대해 알아보도록 하겠습니다.\n먼저 halyard에서 언제 인터넷과 통신하는지를 대강 추려보도록 하겠습니다.\n Spinnaker의 version.yaml을 불러와서 최신의 halyard 버전과 최신 Spinnaker의 버전들을 보여줍니다. gs://halconfig/version.yml 설치하고자 하는 Spinnaker의 버전을 선택하면, 그에 따른 배포에 필요한 yaml들을 불러옵니다. gs://halconfig/bom/VERSION.yml gs://halconfig/MICRO_SERVICE/TAG.yml deploy를 하기 위해 Google Cloud Repository에서 이미지를 가지고 옵니다. gcr.io/spinnaker-marketplace/SERVICE 마지막으로 dependency가 있는 몇가지 서비스를 Google Cloud Repository에서 가지고옵니다. (consul, redis, vault) gcr.io/kubernetes-spinnaker/SERVICE 여기서 local 설정으로 변경이 가능한 것은 2020.01.20 현재 1,2,3번 항목들입니다. 이것들을 어떻게 인터넷이 되지 않는 환경에서 설치가 가능하도록 설정하는지에 대해 알아보겠습니다.\ngsutil로 gs://halconfig 파일들을 로컬에 복사하기 우선 인터넷이 잘 되는 서버가 하나 필요합니다. 이 서버에서 우리는 필요한 BOM(Bill of Materials)를 미리 다운로드 할 것입니다.\ngsutil이 설치되어 있어야 합니다.\ngsutil -m cp -r gs://halconfig . 이렇게하면 로컬에 halconfig라는 폴더가 생겼을 것입니다. 이를 인터넷이 안되는 halyard가 설치된 서버로 복사합니다. 이때, halconfig 폴더 내의 내용들은 ~/.hal/.boms/ 폴더 내에 복사합니다.\n$ ls ~/.hal/.boms/ bom clouddriver deck echo fiat front50 gate igor kayenta monitoring-daemon orca rosco versions.yml 여기서 rosco/master 폴더로 들어가면 packer.tar.gz라는 폴더가 있습니다. 이를 rosco 폴더로 옮기고 압축을 해제합니다.\nmv ~/.hal/.boms/rosco/master/packer.tar.gz ~/.hal/.boms/rosco cd ~/.hal/.boms/rosco tar xvf packer.tar.gz halyard에서 gcs의 version.yml이 아닌 로컬의 version.yml을 참조하도록 설정 기본적으로 halyard는 gs://halconfig/version.yml을 참조하려 할 것입니다. 이를 local:이라는 접두사를 붙여 로컬을 바라보게 할 수 있습니다.\nhal config version edit --version local:1.17.4 그리고 halyard가 gcs를 바라보지 않도록 설정합니다.\n# /opt/spinnaker/config/halyard-local.yml spinnaker: config: input: gcs: enabled: false 그러고 난 뒤, 각 서비스들의 BOM도 로컬을 바라보게 설정해야 합니다. 아까 위에서 1.17.4 버전을 사용한다고 했으니, 해당 yaml파일을 열고 local: 접두사를 추가합니다.\nartifactSources: debianRepository: https://dl.bintray.com/spinnaker-releases/debians dockerRegistry: gcr.io/spinnaker-marketplace gitPrefix: https://github.com/spinnaker googleImageProject: marketplace-spinnaker-release dependencies: consul: version: 0.7.5 redis: version: 2:2.8.4-2 vault: version: 0.7.0 services: clouddriver: commit: 024b9220a1322f80ed732de9f58aec2768e93d1b version: local:6.4.3-20191210131345 deck: commit: 12edf0a7c05f3fab921535723c8a384c1336218b version: local:2.13.3-20191210131345 defaultArtifact: {} echo: commit: acca50adef83a67e275bcb6aabba1ccdce2ca705 version: local:2.9.0-20191029172246 fiat: commit: c62d038c2a9531042ff33c5992384184b1370b27 version: local:1.8.3-20191202102650 front50: commit: 9415a443b0d6bf800ccca8c2764d303eb4d29366 version: local:0.20.1-20191107034416 gate: commit: a453541b47c745a283712bb240ab392ad7319e8d version: local:1.13.0-20191029172246 igor: commit: 37fe1ed0c463bdaa87996a4d4dd81fee2325ec8e version: local:1.7.0-20191029183208 kayenta: commit: 5dcec805b7533d0406f1e657a62122f4278d665d version: local:0.12.0-20191023142816 monitoring-daemon: commit: 59cbbec589f982864cee45d20c99c32d39c75f7f version: local:0.16.0-20191007112816 monitoring-third-party: commit: 59cbbec589f982864cee45d20c99c32d39c75f7f version: local:0.16.0-20191007112816 orca: commit: b88f62a1b2b1bdee0f45d7f9491932f9c51371d9 version: local:2.11.2-20191212093351 rosco: commit: 269dc830cf7ea2ee6c160163e30d6cbd099269c2 version: local:0.15.1-20191202163249 timestamp: \u0026#39;2019-12-12 14:34:16\u0026#39; version: 1.17.4 이렇게 설정하면 echo를 예로 들 때 ~/.hal/.boms/echo/2.9.0-20191029172246/echo.yml을 참조하게 될 것입니다.\n배포에 필요한 이미지들을 private registry에 불러오기 이제 실제 배포에 필요한 이미지를 로컬로 복사해두어야 합니다. 저는 내부에서 사용하는 docker registry에다가 저장해 둘 것입니다. 인터넷이 되는 서버에서 다음과 같이 작업하면 됩니다.\ndocker pull gcr.io/spinnaker-marketplace/SERVICE:TAG docker tag gcr.io/spinnaker-marketplace/SERVICE:TAG private-docker-registry/repository-name/SERVICE:TAG docker push private-docker-registry/repository-name/SERVICE:TAG 이렇게 private registry로 저장을 해 두었을 경우 VERSION.yml 파일에서 dockerRegistry 항목을 수정합니다.\nartifactSources: debianRepository: https://dl.bintray.com/spinnaker-releases/debians #dockerRegistry: gcr.io/spinnaker-marketplace dockerRegistry: private-docker-registry/repository-name gitPrefix: https://github.com/spinnaker googleImageProject: marketplace-spinnaker-release 또는 docker pull을 이용해서 이미지를 다운받고, 이를 docker save 명령어를 통해 tar.gz 파일로 변환한 뒤, Kubernetes의 모든 워커노드에서 이를 이리 docker load 하는 방법도 있습니다. 이렇게 하면 이미 로컬에 있는 이미지이기 때문에 외부로 접속하지 않습니다.\ndocker pull gcr.io/spinnaker-marketplace/SERVICE:TAG docker save -o SERVICE.tar.gz gcr.io/spinnaker-marketplace/SERVICE:TAG scp SERVICE.tar.gz TARGET_IP:~/path/to/target ssh TARGET_IP docker load -i ~/path/to/target/SERVICE.tar.gz 이번에는 dependency와 관련된 이미지를 불러와야 합니다. 먼저 Image Registry를 변경하기 위해서는 다음과 같이 조치합니다.\n ~/.hal/default/service-settings/redis.yml파일을 생성합니다. 다음과 같이 작성합니다. artifactId: private-docker-registry/repository-name/redis-cluster:v2 이 다음에는 마찬가지로 image를 pull하고 이를 private docker registry로 push합니다.\ndocker pull gcr.io/kubernetes-spinnaker/SERVICE:TAG # redis-cluster:v2 docker tag gcr.io/kubernetes-spinnaker/SERVICE:TAG private-docker-registry/repository-name/SERVICE:TAG docker push private-docker-registry/repository-name/SERVICE:TAG 또는 이미지를 tar로 묶어서 복사하는 방법도 있습니다.\ndocker pull gcr.io/kubernetes-spinnaker/SERVICE:TAG docker save -o SERVICE.tar.gz gcr.io/kubernetes-spinnaker/SERVICE:TAG scp SERVICE.tar.gz TARGET_IP:~/path/to/target ssh TARGET_IP docker load -i ~/path/to/target/SERVICE.tar.gz Image Registry가 kubernetes-spinnaker로 변경된 것을 주의하시면 됩니다.\nDeploy 여기까지 왔으면 모든 준비작업은 끝났습니다. 이제 배포만 하면 됩니다.\nhal deploy apply Reference https://www.spinnaker.io/guides/operator/custom-boms/\nhttps://github.com/spinnaker/spinnaker/issues/3967#issuecomment-522306893\n"},{"uri":"http://kimmj.github.io/kubernetes/kubernetes-service-iptables/","title":"Kubernetes Service는 어떻게 iptables 설정이 되는가","tags":["kubernetes","service","iptables"],"description":"","content":"kube-proxy 는 daemonset 으로 각각의 노드에 모두 떠있다. 역할은 kubernetes에서의 service 가 가지고 있는 Virtual IP 로 트래픽을 전달할 수 있도록 적절한 조작을 해주는 것이다. 기본적으로 3가지 모드가 있으나, 일반적으로는 iptables 모드를 많이 사용한다.\n User space\n iptables\nLinux Kernel 에서의 netfilter 를 사용하여 kubernetes 서비스에 대한 라우팅을 설정하는 것이다. 이 모드가 default 옵션이다. 여러개의 pod 로 트래픽을 load balancing 할 때 unweighted round-robin scheduling 을 사용한다.\n IPVS(IP Virtual Server)\nnetfilter framework 에 속해있는 것으로, IPVS 는 least connection이나 shortest expected delay와 같은 layer-4 load balancing algorithm을 지원한다.\n kubernetes에서 Service 가 만들어지면 kube-proxy 는 netfilter chain을 설정하여 connection이 node의 kernel에 의해 바로 pod-container의 endpoint까지 가도록 한다.\nService로의 통신 KUBE-MARK-MASQ 는 masquerade를 이용하여 Serivce 로 이동하는 패킷이 cluster의 외부에서 들어온 패킷임을 표시한다. 이렇게 마킹이 된 패킷은 POSTROUTING 룰에서 Source IP를 해당 노드의 IP로 SNAT 을 할 때 사용된다.\n KUBE-SVC-XXX 는 Service 로 가는 모든 트래픽에 대해 적용이 되며, 각 Serivce 의 Endpoint 로 가는 rule을 정의한다. 어디로 갈지는 랜덤으로 보낸다.\n KUBE-SEP-XXX\nKUBE-MARK-MASQ 를 사용하여 필요할 경우 SNAT 을 해야한다고 표시한다.\n그 다음 DNAT 을 통해 Endpoint 로 목적지를 변경한다.\n KUBE-MARK-DROP\n앞선 rule 들에 의해 DNAT 이 되지 않은 패킷들에 대해서 netfilter 에 마킹을 한다. 이 패킷들은 KUBE-FIREWALL chain 에 의해 버려진다.\n 이렇게 할 경우 Endpoint 가 되는 Pod 가 각각의 노드에 있을 때, 어떤 곳을 더 선호한다던지 그런 규칙이 없다. 다만, externalTrafficPolicy: Local 을 설정하게 되면 요청을 받은 Node에 실제로 Endpoint 로 사용될 Pod 가 없게되면 connection이 refuse 된다.\nNodePort로의 통신 NodePort 를 사용하게 되면 kube-proxy 는 --service-node-port-range 에 의해서 설정된 값들 중 임의로 하나를 선택하여 열리게 된다. 이 때, Kubernetes가 아닌 다른 프로세스에 의해 점유되는 것을 막기 위하여 이 포트를 bind하고 listen하게 된다. 만약 NodePort 를 사용하기 위해 포트를 bind하기 전 다른 프로세스가 bind를 해버린다면, NodePort 의 할당은 에러로그를 출력할 것이다.\n만약 kube-proxy 가 죽어있는 상태에서(bind 가 풀리게 될 것이다) NodePort 와 동일한 포트를 다른 프로세스에서 bind, listen 을 하게되면 어떻게 될까? 이 경우에도 마찬가지로 Kubernetes의 Endpoint로 패킷이 전달된다. 이는 iptables 의 nat table에서 PREROUTING chain 때문이다.\nPREROUTING chain PREROUTING chain은 Linux kernel의 networking stack에서의 가장 먼저 발현되는 rule이다.\nkube-proxy 가 생성한 PREROUTING chain은 패킷이 현재 노드의 local socket으로 가야하는지, 아니면 파드로 포워딩해야하는지 결정해준다. 따라서 NODE_IP:NODE_PORT 로의 요청은 해당 노드에서 해당 파드가 아닌 다른 프로세스가 그 port를 사용하고 있다고 하더라도, 항상 파드로 갈 수 있게 된다.\nPREROUTING 을 통해 NodePort로 사용중인지 확인하고, 파드로 보낼지 아니면 다른 프로세스로 보낼지 결정한다.\nReference link https://ronaknathani.com/blog/2020/07/kubernetes-nodeport-and-iptables-rules/ https://letslearn24x7.blogspot.com/2020/10/lets-learn-about-kube-proxy-in-iptables.html https://kubernetes.io/ko/docs/tutorials/services/source-ip/ "},{"uri":"http://kimmj.github.io/kubernetes/concepts/kubernetes-components/","title":"Kubernetes Components","tags":["kubernetes","components"],"description":"","content":"각 Components 에 대해 알아보자.\nControl Plane Component ETCD partition tolerance(분할 내성)보다 consistency(일관성)에 중점을 둔 db. ETCD 는 간단한 unstructured value를 저장하기에 좋다.\nconsistency를 중요하게 여기기 때문에 write의 순서를 엄격하게 규정하여 set value 시 atomic한 update를 제공한다.\nclient는 특정한 key namespace에 대해 subcription을 하여 변화를 감지할 수 있다. 따라서 어떤 component가 ETCD에 write를 할 경우 다른 component는 즉각적으로 그 변화에 대응할 수 있다.\nkube-apiserver Kubernetes에서 ETCD와 통신하는 유일한 시스템이다. ETCD로의 접근 시도에 대해 필터링을 한다.\nKubernetes API를 노출하는 Control Plane Component 이다.\nController Manager API Server와 통신을 하는 것.\nController는 ReplicaSet 과 같은것으로, ReplicaSet을 예로 들면 ReplicaSet 의 resource를 watch하고 그 selector를 기반으로 Pod 를 watch한다. 그렇게 해서 ReplicaSet 의 desired state가 되도록 Pod 를 조작한다.\n각 Controller는 논리적으로 개별 프로세스이지만 복잡성을 낮추기 위해 단일 바이너리로 컴파일 되고 단일 프로세스 내에서 실행된다.\n 노드 컨트롤러: 노드가 다운되었을 때 통지와 대응에 관한 책임을 가진다. 레플리케이션 컨트롤러: 시스템의 모든 레플리케이션 컨트롤러 오브젝트에 대해 알맞은 수의 파드들을 유지시켜 주는 책임을 가진다. 엔드포인트 컨트롤러: 엔드포인트 오브젝트를 채운다(즉, 서비스와 파드를 연결시킨다.) 서비스 어카운트 \u0026amp; 토큰 컨트롤러: 새로운 네임스페이스에 대한 기본 계정과 API 접근 토큰을 생성한다. kube-scheduler Node에 할당되지 않은 Pod 검색 (unbound Pods) Cluster의 현재 state를 확인 (memory에 있는 cache) 여유 공간이 있고 다른 constraint를 만족하는 노드를 선택 파드를 노드에 할당 Node Plane Component kubelet Node에 위치하게 되는 agent이다.\n자신이 위치한 Node에 있는 Pod 를 watch하고 있어야 할 책임이 있어, 이러한 Pod 들이 Running 상태가 되도록 만들어야 한다. 이러한 Pod 에 변경점이 생기면 report를 한다.\nkube-proxy Node에서 network rule 들을 관리한다. User space, iptables, IPVS 모드가 있으며, 모드에 따라 적절하게 netfilter 등을 조작한다. 서비스에 Virtual IP 를 할당하고, 이 곳으로 SNAT 과 DNAT 을 통해 패킷이 전달되도록 하는 역할이다.\niptables 의 rule을 생성하여 Service 로 전달되는 request가 적절한 Pod 로 라우팅 되도록 해주는 역할이다.\nContainer Runtime Container의 실행을 담당하는 소프트웨어\nAppendix links https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca https://blog.2dal.com/2018/03/28/kubernetes-01-pod/ "},{"uri":"http://kimmj.github.io/kubernetes/kubernetes-cpu-request-limit/","title":"Kubernetes에서의 cpu requests, cpu limits는 어떻게 적용될까","tags":["kubernetes","k8s","resources","cpu-manager"],"description":"","content":"Kubernetes 에서는 컨테이너 단위로 resource를 할당할 수 있다. 여기에는 memory, cpu, ephemeral-storage, hugepages 등이 포함된다. 이 중에서 cpu 의 requests, limits 가 어떤 방식으로 적용이 되는지에 대해 알아볼 것이다.\nLinux Kernel 먼저 기본적으로 Kubernetes는 Linux Kernel의 cgroup을 사용하여 리소스 할당을 한다. cgroup은 control groups 의 의미를 가지며 프로세서들의 자원(cpu, memory 등)을 제한하는 기술이다.\nCPU Share cpu.shares는 CPU를 다른 group에 비해 상대적으로 얼마나 사용할 수 있는지를 나타내는 값이다. 예를 들어 하나의 CPU를 가지고 있고, 두개의 group이 있다고 해보자. 다음과 같이 cpu.shares를 설정했다고 생각해보자.\nA: 100 B: 200 B는 A보다 2배의 cpu.shares 값을 가지고 있다. 따라서 B group은 A group에 비해 두배 더 CPU를 사용할 수 있게 되고, CPU 관점에서 보면 B는 2/3, A는 1/3 만큼의 CPU를 사용할 수 있다.\n이번엔 좀 더 복잡한 예시를 보도록 하겠다.\ngraph TD A[A\u0026lt;br/\u0026gt;1024] --- A1[A1\u0026lt;br/\u0026gt;512] A --- A2[A2\u0026lt;br/\u0026gt;1024] B[B\u0026lt;br/\u0026gt;2048] --- B1[B1\u0026lt;br/\u0026gt;2048] GROUP SHARES A 1024 B 2048 A1 512 A2 1024 B1 2048 위의 예시에서 첫번째 level의 group을 보면 A:B 는 1:2 로 cpu.shares 값이 정의되어 있다. 이 의미는 곧 두 group이 동시에 CPU time을 사용하려 할 때, B 는 A 에 비해 2배 더 CPU time을 사용한다는 의미이다. 만약 동시에 사용하지 않고 A 만 사용하려고 하는 순간에는 당연히 A 혼자 모든 CPU를 점유할 수 있게 된다.\n두번째 level의 group을 보면 A1 과 A2 는 A 의 child group이다. 따라서 A 에게 할당된 CPU time에서 A1 과 A2 는 서로 1:2 의 비율로 CPU time을 가져간다. A1 과 A2 만 동작하고 있다면, A1 의 CPU time은 다음과 같이 계산된다.\n# A CPU ratio 1024(A)/1024(A) = 1 # A1 CPU ratio in peer group cpu.shares total: 512(A1)+1024(A2)=1536 A1 cpu.shares: 512 allocated CPU ratio: 512/1536 = 1/3 # A1 CPU in total 1 * 1/3(A1 cpu ratio in peer group) = 1/3 만약 B 도 함께 동작한다면 다음과 같이 계산한다.\n# A CPU ratio 1024(A)/1024(A)+2048(B) = 1/3 # A1 CPU ratio in peer group cpu.shares total: 512(A1)+1024(A2)=1536 A1 cpu.shares: 512 allocated CPU ratio: 512/1536 = 1/3 # A1 CPU in total 1/3(A CPU ratio) * 1/3(A1 cpu ratio in peer group) = 1/9 CFS Quota CFS는 Completely Fair Scheduler의 약자이다.\ncpu.cfs_quota_us, cpu.cfs_period_us로 CPU time에 제약을 건다. 먼저 각각을 소개하자면 cpu.cfs_quota_us는 CPU를 사용할 수 있는 시간을 의미한다. cpu.cfs_quota_us를 10000us(=10ms)으로 설정할 경우 해당 group은 10ms 시간만큼만 CPU를 사용할 수 있다. 당연하게도, cpu.cfs_quota_us를 통해 설정된 사용가능한 CPU time은 특정 주기로 복구된다. 이를 cpu.cfs_period_us라고 한다.\n다음의 예를 보자.\ncpu.cfs_quota_us: 10000 cpu.cfs_period_us: 100000 cpu.cfs_period_us가 100000us(=100ms)로 설정되었다. 그 말은 group이 사용한 CPU time이 100ms 주기로 초기화된다는 것을 의미한다. cpu.cfs_quota_us는 10000us(=10ms)로 설정되었다. 따라서 group은 100ms 동안 10ms까지 CPU를 사용할 수 있다는 것을 의미하고, CPU 관점에서 보았을 때 이는 0.1 CPU를 사용한다는 의미이다.\n그렇다면 자신에게 주어진 quota를 다 사용했음에도 불구하고, CPU를 사용하려하면 어떻게 될까? 이 경우 다음 period 주기가 돌때까지 CPU를 사용하지 못하게 되며, 이를 throttle이라고 한다.\n얼마나 throttle이 되었는지를 확인하려면 cpu.stats를 확인하면 된다.\nKubernetes 이번에는 Kubernetes가 어떻게 위의 두가지를 이용하여 cpu requests와 cpu limits를 다루는지에 대해 알아볼 것이다. Kubernetes 는 CPU에 대해서 compressible 한 자원이라고 해석한다. 반면 메모리는 임의로 줄일 수 없는 영역이기때문에 uncompressible 한 자원이다.\nCPU Requests Kubernetes 는 cpu.shares 를 통해 할당된 requests.cpu 를 적용한다.\nKubernetes 는 cpu requests 값을 우선 milicore 단위로 환산한다. 공식 docs에서는 1 core = 1000 milicore 라고 명시되어 있다. 여기서 milicore 로 환산한 값을 1000으로 나누고 1024를 곱한다. 예를 들어 파드가 requests.cpu 값으로 500m 을 주었다면, 500/1000*1024 = 512 계산식을 통해 512라는 값이 나오게 된다. 이 값을 cgroup 의 cpu.shares 값으로 사용한다.\n앞서 말했듯이 cpu.shares 는 다른 group과의 상대적인 cpu time을 말하는 것이다. 따라서 만약 다른 group에서 큰 숫자로 cpu.shares 를 사용하게 된다면, 쿠버네티스에서 설정한 requests.cpu 값보다 적게 cpu time이 할당될 것이다.\n다시 이러한 의문이 생길 수 있다. 상대적인 값으로 설정한 것들이 어떻게 쿠버네티스에서는 requests.cpu 의 의미로 사용되는 것일까? 이는 쿠버네티스에서 자체적으로 설정한 requests.cpu 즉, 각 노드에서의 cpu.shares값을 관리하기 때문이다. 쿠버네티스는 Allocatable한 cpu 갯수에 1024 를 곱하여 cpu.shares 값들의 합이 그 값을 넘을 수 없도록 관리한다.\n예를 들어 10개의 cpu가 있는 노드라면 cpu.shares 의 합은 10240 을 넘을 수 없다. 이 상황에서 다음과 같이 requests.cpu 를 설정했다고 해보자.\nCONTAINER CPU REQUESTS A 1500m B 3500m C 5000m A 의 cpu.shares 값은 1500/1000*1024 = 1536 이다. B 의 cpu.shares 값은 3500/1000*1024 = 3584 이다. C 의 cpu.shares 값은 5000/1000*1024 = 5120 이다. 이 값들을 모두 합하면 1536+3584+5120 = 10240 이 된다. 이 경우 다른 group 에서 cpu.shares 값이 할당되지 않았다면, 상대적으로 A, B, C 컨테이너는 각각 1.5:3.5:5 의 비율로 cpu slice를 사용할 수 있다. 즉, 바꿔말하면 각각 1.5 cpu, 3.5 cpu, 5 cpu 를 사용할 수 있는 것이다.\ncpu.shares 는 상대적인 값을 의미한다는 특징 떄문에, 쿠버네티스처럼 노드에서 최대 cpu.shares 의 합을 관리하지 않는다면 cpu.shares 값은 앞서 보았던것 처럼 cpu requests 의 의미를 가지기 어렵다. 원래 의미 그대로 단순히 상대적인 cpu slice 사용량을 의미할 뿐이다.\nCPU Limits Kubernetes는 CFS 를 통해 설정된 limits.cpu 를 적용한다.\nCPU Requests 에서 다루었던 것처럼 CPU Limits 도 값이 주어지면 이를 milicore 단위로 환산한다. 이 값을 통해 cpu.cfs_quota_us 를 설정한다. Kubernetes 는 cpu.cfs_period_us 값을 100000us 로 고정시켜놓는다. 이 경우 1 cpu 까지 사용하고 싶다면 cpu.cfs_quota_us 를 동일한 값인 100000us 로 할당한다. 1 cpu = 1000 milicore 이므로 결론적으로는 환산된 milicore 값에 100을 곱한다는 것을 알 수있다.\n예를 들어, limits.cpu 로 3 을 할당했다고 생각해보자. 그러면 먼저 milicore 단위로 환산하게 되고 3000m 이라는 값이 나온다. 여기에 100을 곱한 300000 값이 cpu.cfs_quota_us 로 설정되게 된다. 이때 \u0026ldquo;100ms 시간동안 3 개의 코어를 온전히 사용한다 (3 cpu 에서 100% 로 사용중이다)\u0026ldquo;라고 해석하기보다는, 100ms 시간동안 사용할 수 있는 모든 cpu에서 사용된 cpu time의 합이 300ms 이다(6 cpu 에서 각각 50% 씩 사용할 수도 있다)라고 해석해야한다. 만약 이 시간보다 더 많이 CPU를 사용하려고 요청한다면, throttle 현상이 생길 것이다. 이는 cpu.stat 을 확인하면 알 수 있다.\n그렇다면 limits.cpu 를 설정하지 않았을 때는 어떤 현상이 발생할까? 이 경우 cpu.cfs_quota_us 값이 -1 로 설정되며 이는 제한을 두지 않겠다는 의미이다. 따라서 자신이 사용할 수 있는 만큼 계속해서 CPU를 사용하게 될 것이다.\n참조 https://www.redhat.com/sysadmin/cgroups-part-two https://www.batey.info/cgroup-cpu-shares-for-kubernetes.html https://www.batey.info/cgroup-cpu-shares-for-docker.html https://medium.com/@betz.mark/understanding-resource-limits-in-kubernetes-cpu-time-9eff74d3161b "},{"uri":"http://kimmj.github.io/ubuntu/split-tgz/","title":"Linux에서 압축파일 분할하기","tags":["ubuntu","tgz","tar","split"],"description":"","content":"Linux 환경에서 압축파일을 분할하는 방법에 대해 알아볼 것이다.\nPrerequisite tar binary installed split binary installed cat binary installed How to split 다음은 이번에 할 압축분할에 대한 간단한 flow 이다.\ngraph TD A[Files] --\u0026gt;|Compress with tar| B(tar output) B --\u0026gt; |Split file with split| C(splited files) C --\u0026gt; |Join splitted files with cat| D(single file) D --\u0026gt; |Express with tar| E(Files) Compress with tar 먼저 우리가 알고있는 일반적인 방법으로 압축을 하여 하나의 파일로 만든다.\nCOMPRESSED_FILENAME=docker.tgz SRC_FILES=folder tar cvzf $COMPRESSED_FILENAME $SRC_FILES Split file with split split 이라는 프로그램을 통해 하나의 파일을 여러개의 파일로 나눈다. 이 때 -b 옵션을 주면 얼만큼의 사이즈로 나눌지를 선택할 수 있다. 기타 다른 옵션들도 있으니 필요한 상황에 따라 옵션을 찾아보면 좋을 듯 하다.\nFILESIZE=2G SRC_FILENAME=docker.tgz DST_FILENAME=docker.tgz.part split -b $FILESIZE $SRC_FILENAME $DST_FILENAME 이제 docker.tgz.part 라는 prefix를 가진 파일들이 여러개 생길 것이다. 이 파일들의 사이즈는 -b 옵션으로 주었던 사이즈랑 일치하는 것을 확인할 수 있다.\nJoin splitted files with cat cat 은 일반적으로 파일의 내용을 출력할 때 사용한다. 이를 redirection과 함께 사용하여 split 으로 나누었던 파일들을 하나로 합친다.\nSRC_FILENAME=docker.tgz.part DST_FILENAME=docker.tgz cat ${SRC_FILENAME}* \u0026gt;$DST_FILENAME 이렇게 하면 DST_FILENAME 으로 지정한 docker.tgz 파일이 생긴 것을 확인할 수 있다.\nExpress with tar 이제 tar 를 통해 다시 압축해제하면 처음에 압축했던 파일들을 다시 확인할 수 있다.\nFILE_NAME=docker.tgz tar xvzf $FILE_NAME "},{"uri":"http://kimmj.github.io/ubuntu/samba/","title":"Samba를 통한 디스크 공유","tags":["samba"],"description":"","content":"Samba는 리눅스에 있는 폴더를 윈도우와 공유하기 위해 사용한다. 최초에 NFS로 구성했으나 NFS는 다음과 같은 문제점이 있었다.\n 폴더 전체를 rwx 권한을 주게 만들거나 nobody:nogroup으로 모든 권한을 폴더에 줘야 했다. perforce와 연동하려고 구성한 것이었는데 1번의 문제로 인해 자꾸 충돌이 발생했다. 이때문에 다른 대안을 찾던 중 samba를 알게 되었고, 이를 내 컴퓨터에도 적용해보았다.\nSamba 구성 samba 설치 apt install samba -y samba 설정 /etc/samba/smb.conf 파일을 열어 수정한다.\n[workspace] # 여기서 workspace는 접근할 때 사용되는 이름 comment = workspace path path = /path read only = no ritable = yes browseable = yes guest ok = yes available = yes public = yes valid users = root user 추가 실제 passwd에 등록된 계정으로 설정해야 한다.\n$ smbpasswd -a root New SMB password: Retype new SMB password: Added user root. $ service smbd restart 네트워크 드라이브 연결 주소: \\\u0026lt;서버 IP\u0026gt;\\workspace\n이런식으로 네트워크 드라이브를 연결하면 될 것이다. workspace는 위에서 /etc/samba/smb.conf에서 사용한 이름으로 대체될 수 있다.\n"},{"uri":"http://kimmj.github.io/coredns/configure-dns-server/","title":"CoreDNS를 통한 Dns Server 구축하기 with docker","tags":["docker","docker-compose","network","bridge","container"],"description":"","content":"CoreDNS는 kubernetes에서 dns resolve를 위해 사용하고 있는 오픈소스이다. 실제로 kubernetes의 kube-system 네임스페이스를 보면 coredns-xxx 파드가 떠있는 것이 보일 것이다.\n사내에서 CoreDNS를 통한 DNS 서버를 구축하며 알게된 사실들, 구축 방법등을 공유한다.\nCoreDNS를 시작하는 것은 정말 어렵지 않다. docker를 통해 컨테이너만 실행시켜도 우선은 CoreDNS를 맛볼 수 있다. 다만 나의 경우 docker를 그 자체로 실행시키는 것 보다 docker-compose를 통해 실행시키는 것을 더 선호하는 편이다. docker-compose의 장점이라면 복잡한 docker 명령어를 일일이 기억하고 있지 않아도 docker-compose up -d 명령어 하나로 사전에 설정된 명령을 실행시킬수 있다는 것이다.\nCoreDNS의 docker-compose.yml 구성은 구글링으로도 쉽게 구할 수 있으며, 내가 구한 설정은 다음과 같았다.\nversion: \u0026#39;2.1\u0026#39; services: coredns: image: coredns/coredns restart: always command: -conf /root/coredns/config/Corefile ports: - 53:53/udp volumes: - ./config:/root/coredns/config/ volumes를 보면 config 폴더를 docker-compose.yml 파일과 함께 위치했다는 사실을 알 수 있다. 따라서 다음과 같은 디렉토리 구성을 한다.\n$ tree . ├── config │ ├── Corefile │ ├── kubeconfig │ └── my.host.name.db └── docker-compose.yml 여기서 kubeconfig는 CoreDNS의 kubernetes 플러그인을 사용해보기 위한 목적으로, 어떻게 kubernetes에서 DNS resolve를 하는 것이 밖에서도 볼 수 있는지 확인해보려고 구성해 보았다. 물론 클러스터 외부에서는 kubernetes의 서비스 대역과 통신이 불가능하기 때문에 거의 사용할 일이 없다.\nmy.host.name.db는 DNS resolve를 위한 DNS db 파일이다.\nCorefile은 CoreDNS의 설정파일로, 매칭되는 Domain Name에 대해 어떤 rule로 DNS resolve를 할지 결정하는 파일이다.\n나는 다음과 같이 Corefile을 구성했다.\nmy.host.name { file /root/coredns/config/my.host.name.db cache log errors } xx.x.xx.172.in-addr.arpa { whoami } . { forward . 8.8.8.8 log cache errors } 설정을 하나씩 보면 우선 my.host.name을 가지는 Domain에 대해서는 my.host.name.db 파일을 읽도록 해 두었다.\nxx.x.xx.172.in-addr.arpa 의 경우 DNS server의 아이피로 hostname을 알려주고 싶을 경우 설정하는 것이다.\n.으로 설정한 부분은 default 세팅이라고 봐도 무방하다. 여기에서는 8.8.8.8로 위에 설정한 두개의 Domain 빼고는 모두 포워딩한다. 즉, 내가 resolve를 하는게 아니라 8.8.8.8에서 resolve한 결과값을 리턴하겠다는 뜻이다.\n위와같이 설정을 하고 난뒤 다음과 같이 입력한다.\ndocker-compose up -d 내 서버로 resolve를 하고싶다면, netplan의 nameserver 부분을 수정하거나 /etc/resolv.conf를 수정하면 된다.\n/etc/resolv.conf에 관해서는 다른 페이지에서 다뤄보도록 한다.\n"},{"uri":"http://kimmj.github.io/harbor/what-is-harbor/","title":"Private Docker Registry 오픈소스: Harbor란","tags":["harbor","docker-registry"],"description":"","content":"Harbor란 Harbor 는 올해(20년) 중순 쯤 CNCF 의 graduated된 오픈소스 프로젝트로 Docker Hub 처럼 이미지를 저장할 수 있는 저장소이다. Docker 는 이미 docker registry 라는 이름으로 개인 이미지 저장소를 컨테이너화 하였었다. Harbor 는 내부적으로 이 docker registry 를 사용하고 있으며, 여기에 RBAC, web page, image scan 같은 편리한 기능을 추가하였다. 외부 서비스를 이용하는 것이 아니라 내부에 이미지 파일을 저장하기 때문에 사내 보안 정책에도 알맞게 사용할 수 있다. 아니면 필요에 따라 S3나 Minio를 사용할 수 있을듯 하다.\n특히 20년 11월부터 Docker Hub의 기본 정책이 image pull rate를 제한하는 것이 되었는데, 사내에서는 보통 forward proxy를 이용하므로 이러한 image pull rate를 초과할 가능성이 매우 높다. 이러한 경우에 Harbor와 같은 private docker registry를 구축하면 큰 도움이 될 것이다.\n주요 기능 사내에서 사용하며 편리하다고 생각되었던 몇가지 기능들을 소개한다.\n 이미지 복제 (Harbor 외부 \u0026lt;-\u0026gt; Harbor 내부) 이미지 주기적 삭제 (tag retention) Project 별 Disk quota 기능 Project 별 Private/Public 기능 Webhook 기능 (v1.10 이상에서 가능) OCI Support (v2.0 이상에서 가능) docker registry 의 API 사용 가능 (인증 관련해서만 코딩에 주의) Swagger 를 통한 API Docs 제공 개인적인 생각 다른 docker registry 서비스들(예를 들면 Artifactory 라던지, AWS ECR 이라던지..)을 사용해보진 않았지만, 잘 만들어진 프로젝트라고 생각된다. 업데이트도 자주 이루어 지고 있는 중이고 이슈도 등록하면 금방 확인해주는 듯 하다. 특히나 Graduated 된 상태이니 믿고 사용해도 좋을 정도라고 생각된다. 나는 사내에서 용도별로 여러개의 Harbor 를 설치하고 운용중인데, 설치 과정이 매우 쉬워서 새롭게 시작하려는 사람도 테스트를 해보기 용이할 듯 하다.\ndocker registry 를 사용하다보면 당연스럽게도 아쉬운 부분이 GUI에 관한 부분인데 Harbor 는 GUI로 많은 것들을 할 수 있어서 정말 편리하다. 이미지간 복제도 GUI로 할 수 있었고(나의 회사에서는 VPN으로 연결된 구간이 있었는데, Harbor 가 컨테이너 기반이라서 그런지 VPN을 타고 트래픽이 나가지 못했다. 그래서 결국은 그 구간만 스크립트를 짜서 이미지 복제를 하였다), 기본적인 이미지 삭제나 특정 기간을 지난 이미지 삭제 또는 가장 최근에 Pull된 N개의 이미지만 빼고 삭제하기 등 운용하기에 편리한 기능들도 있다.\n내 생각엔 일단, 한 번 써보면 좋을 프로젝트인 듯 하다.\n"},{"uri":"http://kimmj.github.io/kubernetes/cka-epilogue/","title":"CKA: Certified Kubernetes Administrator 취득 후기","tags":["CKA","Certified-Kubernetes-Administrator"],"description":"","content":"작년부터 미뤄왔던 CKA: Certified Kubernetes Administrator 취득을 드디어 하게 되었습니다. 이제까지 제가 했던 공부들을 공유하며 이 시험을 보려는 사람들에게 유용한 정보를 주기 위해 이 포스트를 작성합니다.\n저는 다음과 같이 CKA를 취득하였습니다.\n 점수: 97점 (커트라인: 74점) 유효기간: 2020.07 ~ 2023.07 (3년) CKA는 CNCF를 리딩하는 Linux Foundation에서 주관하는 시험입니다. 가격은 정가 $300으로 저렴하지는 않은 편입니다. 그러나 할인행사를 많이 하므로 급하지 않다면 시간을 가지고 천천히 결제하는게 좋습니다. 결제 후 1년까지 총 두번의 시험을 치룰 수 있습니다. 두번의 기회 안에만 합격하면 CKA를 취득할 수 있습니다.\n시험 환경 시험은 온라인으로 치루게 됩니다. 해당 시험을 위해서 Chrome Extension을 설치해야 합니다. 이외에도 Requirements가 몇가지 더 있으며 이는 시험을 보기 전에 사이트에서 제공하는 툴로 확인이 가능합니다. 시험 종류중에는 중국/일본어로 진행되는 시험도 있지만 아직 한글은 지원하지 않습니다. 그래서 저도 영어로 지원하였고, 이에따라 감독관도 영어권 사람으로 배정되었습니다. 시험을 위해 마이크 및 웹캠이 필요합니다. 웹캠은 주변 환경을 찍어서 보여줘야 하기 때문에 자유롭게 움직일 수 있어야 합니다. 감독관과는 보통 영어로 대화하지 않고 시험 페이지에서 제공하는 메신저를 통해 텍스트로 대화합니다. 시험 스케줄은 KST로 변환하여 선호하는 시간을 선택하면, 가능한 일정을 알려줍니다. 저의 경우 밤에 집중이 잘 되어 저녁 10시에 응시하였습니다. 취소 및 변경은 응시 하루전까지 자유롭게 가능하고, 24시간 이후가 되었을 경우 패널티를 받게 됩니다. 시험 시간은 총 3시간으로 그 시간 안에 24문제를 풀면 됩니다.\n시험의 난이도는 그렇게 어려운 편은 아닙니다. 물론 저의 경우 약 1년간 쿠버네티스 환경을 익혀왔고, 강의도 학습하였지만 객관적으로 보았을 때에도 시험 난이도가 높지는 않습니다. 가격때문에, 떨어질까봐 걱정되어 망설여지신다면 자신있게 응시해도 좋을 것 같습니다. 어차피 일정 변경이 결제 후 1년간 자유롭기 때문에 막상 시험이 다가왔을 때 공부가 좀 더 필요할 것 같다고 생각되면 취소 후 다시 응시하면 됩니다. 게다가 1개의 추가적인 탭을 사용할 수 있는데, 이 탭으로 kubernetes.io 및 github.com/kubernetes에 접속이 가능합니다. 저의 경우 확실하게 하기 위해 모든 문제를 kubernetes.io에서 검색하여 풀었고, 이렇게 풀어도 시간이 1시간가량 남았습니다.\n준비했던 것들 공식 Docs 먼저 공식 Docs를 보시면 됩니다. 가장 기본이 되는 문서로, 필요한 내용은 전부 Docs에 들어가 있습니다. 내용을 외운다기 보다는 어떤 기능이 있는지 이해하는 방식으로 넘어가면 좋을 것 같습니다. 저의 경우 Concepts만 읽어보았으면 Tasks에 있는 내용은 거의 보지 못했습니다.\nkubernetes the hard way 두번째로 kubernetes-the-hard-way입니다. GCP에 쿠버네티스를 kubeadm이 아닌 직접 프로세스를 실행시키는 방식으로 한땀한땀 구성하는게 특징인 프로젝트입니다. 처음에 개념이 잡히지 않은 상태에서는 해도 단순한 복붙작업이 될 가능성이 높습니다. 따라서 어느정도 공부를 하고 난 뒤 심화학습을 한다는 생각으로 해보시면 좋을 것 같습니다.\nUdemy CKA 강의 세번째로 Udemy에서 하는 CKA 강의입니다. 이 강의에서는 Katacoda를 활용하여 직접 실습도 해볼 수 있어서 배웠던 것을 복습하는 데 매우 좋습니다. 또한 강의 말미에는 Mock Exam이 3개 포함되어 있는데 실제 시험과 난이도가 매우 비슷하고 문제도 크게 다르지 않아서 시험을 준비하는데 많은 도움이 되었습니다. 가격도 계속 2만원 선으로 되어있는 것 같으니 그렇게 큰 부담도 되지 않을 것 같습니다. 저의 경우 회사에서 Udemy 서비스를 지원해주어 이를 통해 강의를 들었습니다. 강의 내용이 평소 혼자 공부할 때에는 넘어갈 수 있는 부분들이 많이 포함되어 있어 부족한 부분을 채우는 데 매우 좋았습니다.\n저는 이렇게 3가지만 공부하고 시험을 치뤘습니다. 심화된 내용을 모두 공부하는 것도 좋겠지만 이정도로만 해도 CKA를 취득하는 데에는 무리가 없습니다.\n공부해야 할 것들 kubectl create 명령어 및 kubectl run(파드 실행) 명령어에 익숙해지기 --dry-run -o yaml 옵션을 통해 대강의 뼈대를 구성하고 kubernetes.io를 참조하며 수정하기 journalctl -u kubelet -f로 kubelet의 로그 확인하기 service kubelet status로 kubelet의 상태 확인하기 static pod를 구성하는 방법 알아보기 (kubelet에서 static pod 위치 지정하는 것, 실제로 static pod를 생성해보는 것) etcdctl을 통해 ETCD를 백업하고 복구하기 kubeadm을 통해 클러스터 구성하고, 업그레이드 하기 kubeadm을 통해 새로운 노드를 워커노드로 붙이기 pv, pvc를 통해 파드에 볼륨 추가하기 taint, toleration, cordon, drain, nodeSelector, affinity를 사용하여 파드가 뜨는 위치를 원하는 대로 조정하기 "},{"uri":"http://kimmj.github.io/jenkins/workspace-list/","title":"Workspace@2를 변경하기 - Workspace List 설정 변경","tags":["jenkins","workspace"],"description":"","content":"운용하는 노드에 executor가 2개 이상이라면, concurrent build 옵션을 disable했다고 하더라도 zombie process가 있을 경우 workspace@2처럼 @ 캐릭터가 들어간 workspace를 사용할 수 있다.\nworkspace 안에 특정한 파일을 넣고 사용하는 경우라면 @2가 생기면 안될 것이다. 그러나 이는 그렇게 좋은 방법은 아닌것 같으며 이런 경우에는 github 등에 스크립트같은 파일을 옮겨놓고 git pull이나 git 관련 플러그인을 통해 다운로드 한 뒤 사용하는 것이 더 좋은 방법인 것 같다.\n하지만 나의 경우 Perforce를 사용하고 있었느데 p4 sync에서 문제가 발생했다. @라는 문자가 없어야 한다는 것이었다. 사실 저 문자만 없다면, workspace의 폴더 이름이 무엇이든 상관이 없었기 때문에 @대신 다른 캐릭터를 사용하기로 결정했다.\nJAVA_OPTS= '-Dhudson.slaves.WorkspaceList=A'를 추가하면 된다. 내 경우 docker-compose를 통해 jenkins를 설치했으므로 다음과 같이 작성했다.\nenvironments: JAVA_OPTS= \u0026#39;-Dhudson.slaves.WorkspaceList=_\u0026#39; 파일에서 envieonments의 정확한 위치는 때에 따라 다를 수 있으므로 검색을 통해 environments의 정확한 위치를 확인해보고 설정하면 될 것 이다. 위와 같이 설정하면 두번째 workspace는 workspace_2와 같이 생길 것이다.\n"},{"uri":"http://kimmj.github.io/kubernetes/port-targetport-nodeport-in-kubernetes/","title":"[번역] 쿠버네티스에서의 Port, TargetPort, NodePort","tags":["kubernetes","service","targetport"],"description":"","content":"원문: https://matthewpalmer.net/kubernetes-app-developer/articles/kubernetes-ports-targetport-nodeport-service.html\n쿠버네티스의 port declaration 필드에는 여러가지가 있다. 각 type에 대해 빠르게 살펴보고 YAML에서 각각 어떤 의미를 가지고 있는지 알아보도록 하자.\nPod ports list pod.spec.containers[].ports로 정의된 이 배열은 container가 노출하고 있는 포트의 리스트를 나타낸다. 이 리스트를 꼭 작성해야할 필요는 없다. 리스트가 비어있다고 하더라도 container가 포트를 listening하고 있는 한 여전히 네트워크 접속이 가능하다. 이는 단순히 쿠버네티스에게 추가적인 정보를 줄 뿐이다.\n List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \u0026ldquo;0.0.0.0\u0026rdquo; address inside a container will be accessible from the network. Cannot be updated. - Kubernets API Docs\n Service ports list 서비스의 service.spec.ports 리스트는 서비스 포트로 요청받은 것을 파드의 어느 포트로 포워딩할 지 설정하는 것이다. 클러스터 외부에서 노드의 IP 주소와 서비스의 nodePort로 요청이 되면 서비스의 port로 포워딩되고, 파드에서 targetPor로 들어온다.\n nodePort 이는 서비스가 쿠버네티스 클러스터 외부에서 노드의 IP 주소와 이 속성에 정의된 포트로 보일수 있도록 한다. 이 때, 서비스는 type: NodePort로 지정해야 한다 이 필드는 정의되어 있지 않을 경우 쿠버네티스가 자동으로 할당한다.\nport 서비스를 클러스터 안에서 지정된 포트를 통해 내부적으로 노출한다. 즉, 서비스는 이 포트에 대해서 보일 수 있게 되며 이 포트로 보내진 요청은 서비스에 의해 선택된 파드로 전달된다.\ntargetPort 이 포트는 파드로 전달되는 요청이 도달하는 포트이다. 서비스가 동작하기 위해서는 어플리케이션이 이 포트에 대해 네트워크 요청을 listening을 하고 있어야 한다.\n"},{"uri":"http://kimmj.github.io/docker/insecure-registry/","title":"http를 사용하는 docker registry를 위한 insecure registry 설정","tags":["docker","insecure-registry","docker-registry"],"description":"","content":"회사같은 곳에서는 보안상의 문제 때문에 Dockerhub에다가 이미지를 올리지 못하는 경우가 많습니다. 이를 위해서 docker에서도 docker registry라는 툴을 제공하는데요, 이는 자신의 local server를 구축하고, dockerhub처럼 이미지를 올릴 수 있는 툴입니다.\n이러한 docker registry는 사용자의 환경에 따라 http를 사용하는 경우가 있습니다. 이 때, docker는 default로 https 통신을 하려 하기 때문에 문제가 발생합니다. 이 경우 다음과 같이 조치를 하면 http 통신을 할 수 있습니다.\n절차 insecure-registry 설정 /etc/docker/daemon.json 파일을 열어 예시처럼 작성합니다. 없을 경우 생성하면 됩니다.\n{ \u0026#34;insecure-registries\u0026#34; : [\u0026#34;docker-registry:5000\u0026#34;] } docker 재시작 # flush changes sudo systemctl daemon-reload # restart docker sudo systemctl restart docker 이제 다시한번 docker pull 명령어를 통해 이미지가 제대로 다운로드 되는지 확인합니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/customize-login-message/","title":"Ubuntu의 Login Message 수정하기","tags":["ubuntu","login-message","motd"],"description":"","content":"TLDR Expand me... This package seeks to make the /etc/motd (Message of the Day) more dynamic and valuable, by providing a simple, clean framework for defining scripts whose output will regularly be written to /etc/motd.\n Ubuntu에서는 /etc/update-motd.d 안에 있는 파일들을 확인하여 console, ssh 등 어떤 방법으로든 로그인했을 때 메시지를 띄워줍니다. 여기서 파일들을 사전순으로 로딩하게 됩니다.\n따라서 해당 폴더에 적절한 파일들을 생성하게 된다면 로그인 시 출력되는 메시지를 조작할 수 있습니다.\n 적용법 /etc/update-motd.d/로 이동 cd `/etc/update-motd.d` 99-message 파일 생성 99-message 이름으로 파일을 생성합니다.\n#!/bin/sh printf \u0026#34;hello. this is customized message.\\n\u0026#34; printf \u0026#34;\\n\u0026#34; 그다음 실행파일로 변경해줍니다.\nchmod +x 99-message 다시 세션 로그인하기 hello. this is customized message. Last login: Wed Mar 11 14:19:56 2020 from x.x.x.x wanderlust@wonderland $ "},{"uri":"http://kimmj.github.io/css/background-img-darken/","title":"background image 어둡게 하기","tags":["css","bgimg-darken"],"description":"","content":"배경 이미지를 삽입했는데 사진이 너무 밝아 어둡게 필터처리를 넣고 싶은 경우가 있을 수 있습니다. 저의 경우 logo에 제 깃허브 프로필사진을 빼고 노을진 풍경을 넣었는데 사진이 너무 밝아 부자연스러운 느낌이 들었습니다.\n이 때 검색 후 다음과 같이 조치를 하여 어두워지는 효과를 줄 수 있었습니다.\n#sidebar #header-wrapper { background-image: linear-gradient( rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3) ),url(../images/sunset_on_ibiza.jpg); } linear-gradient 속성은 선형으로 gradient를 적용하는 속성인데 이를 활용하여 검정색 필터를 넣었습니다. 필수 파라미터로 두개의 색깔이 필요하고, 이를 각각 검정색(rgb(0, 0, 0))에 투명도를 0.3으로 설정한 rgba(0, 0, 0, 0.3)으로 설정하여 이미지가 전체적으로 어둡게 보이도록 설정한 것입니다.\n여담으로, 파워포인트에서 사진을 어둡게 하여 텍스트를 강조하고자 할 때 많이 이용했던 검정색 반투명 박스를 이용하는 것과 비슷한 방법이라고 느껴지네요.\n"},{"uri":"http://kimmj.github.io/hugo/google-analytics/","title":"Hugo에 Google Analytics 적용하기","tags":["hugo","google-analytics"],"description":"","content":"google analytics는 내 블로그를 사용하는 사람들이 얼마나 많은지, 어떤 정보를 보는지 확인할 수 있는 서비스입니다. 무료로 사용할 수 있고, 사용 방법도 어렵지 않기 때문에 search console과 더불어 사용하면 좋은 서비스로 보입니다.\n그래서 저도 제 블로그에 google analytics를 설정할 수 있는지 찾아보던 중 다음과 같은 글을 확인하고, 이를 통해 설정할 수 있었습니다.\nhttps://discourse.gohugo.io/t/implementing-google-analytics-in-hugo/2671/2\n적용 방법 config.toml의 수정 hugo에서는 config 파일을 사용하여 사용자의 설정정보를 보관합니다. 저는 config.toml을 사용하고 있었는데 여기에다가 다음과 같이 추가하면 됩니다.\ngoogleAnalytics = \u0026#34;UA-123-45\u0026#34; 만약 yaml이라면 googleAnalytics: \u0026quot;UA-123-45\u0026quot; 정도로 설정하면 될 것 같습니다.\n여기서 해당 코드를 github에 올릴 때 안전하게 올리고 싶으시다면, git-secret을 참조하시기 바랍니다.\nheader.html에 추가 자신이 사용하는 theme에서 layouts/partials로 이동하면 header.html이라는 파일이 있을 것입니다. 제가 사용중인 Learn 테마에서는 custom-header.html이라는 파일로 사용자가 추가하기를 원하는 내용을 적을 수 있는 파일이 있었습니다. 따라서 저는 그 파일에 다음과 같이 추가해주었습니다.\n{{ template \u0026quot;_internal/google_analytics.html\u0026quot; . }} 또는 async로 이용하길 원한다면 다음과 같이 적으면 됩니다.\n{{ template \u0026quot;_internal/google_analytics.html\u0026quot; . }} 확인 google analytics는 하루에 한번 데이터를 전송합니다. 따라서 하루정도가 지난 후에 google analytics 페이지를 들어가면 통계자료를 열람할 수 있습니다.\n"},{"uri":"http://kimmj.github.io/git/git-secret/","title":"git-secret을 통한 github 파일 암호화","tags":["git-secret","github","git"],"description":"","content":"git을 사용하다 보면 password나 credential같은 정보가 git에 올라가는 경우가 종종 있습니다. aws같은 cloud provider의 crediential을 git에 생각없이 올리고, 이를 다른 해커가 크롤링을 통해 얻어 비트코인을 채굴하는 사례도 있었습니다.\n이처럼 보안이 필요한 파일을 git에 올릴 때, 암호화를 하여 업로드하는 방법이 있습니다.\nhttps://github.com/sobolevn/git-secret\ngit-secret git-secret은 파일에 대한 암화를 지원하기 위해 사용되는 프로그램입니다.\ngit-secret add 명령어를 통해 파일을 암호화하고, git-secret reveal을 통해 복호화합니다. 이 때 gpg를 이용하게 됩니다.\nInstall 사용자 환경에 따라 brew, apt, yum을 통해 설치할 수 있습니다. 또한 make를 통해서 빌드할 수도 있습니다.\n저의 경우 Ubuntu를 사용하고 있고 apt가 있기 때문에 이를 이용하여 설치하였습니다.\napt install git-secret 파일 암호화하기 gpg를 이용하여 키 생성하기 먼저, gpg가 설치되어있는지 확인하고 설치합니다.\n$ gpg -h | grep version License GPLv3+: GNU GPL version 3 or later \u0026lt;https://gnu.org/licenses/gpl.html\u0026gt; # 위와같은 결과가 나오지 않는다면, 설치해야합니다. $ apt install gpg 설치가 되었으면 gpg key를 생성합니다.\n$ gpg --full-generate-key gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Please select what kind of key you want: (1) RSA and RSA (default) (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) Your selection? 1 RSA keys may be between 1024 and 4096 bits long. What keysize do you want? (3072) 4096 Requested keysize is 4096 bits Please specify how long the key should be valid. 0 = key does not expire \u0026lt;n\u0026gt; = key expires in n days \u0026lt;n\u0026gt;w = key expires in n weeks \u0026lt;n\u0026gt;m = key expires in n months \u0026lt;n\u0026gt;y = key expires in n years Key is valid for? (0) 0 Key does not expire at all Is this correct? (y/N) y GnuPG needs to construct a user ID to identify your key. Real name: Ibiza Email address: my@email.com Comment: Wanderlust You selected this USER-ID: \"Ibiza (Wanderlust) \u0026lt;my@email.com\u0026gt;\" Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. 전부 완료되기까지 꽤 시간이 많이 걸립니다.\ngit-secret을 통한 암호화 본격적으로 시작하기 위해 git-secret을 init합니다.\ngit secret init 그 다음 git-secret에 사용자를 추가합니다.\ngit secret tell my@email.com 이제 secret을 통해 암호화할 파일은 원본이 github에 올라가지 않게 하기 위해 .gitignore에 추가하도록 합니다. 저의 경우 ./config.toml이라는 파일을 암호화하기 위해 다음과 같이 추가하였습니다.\nconfig.toml 그 다음 git-secret이 암호화 하도록 파일을 추가합니다.\ngit secret add config.toml 이 때 다음과 같은 에러가 발생했는데, github issue 중 비슷한 문제가 있어 이를 통해 해결할 수 있었습니다.\n$ git secret add config.toml config.toml is not a file. abort. $ git rm --cached config.toml $ git secret add config.toml 1 items added. 이제 hide를 통해 암호화할 수 있습니다.\ngit secret hide 이후 ls를 통해 확인해보면 .secret이라는 postfix가 들어간 파일을 보실 수 있습니다.\n$ ls config.toml* config.toml config.toml.secret git-secret을 이용한 복호화 reveal을 통해 파일을 복호화할 수 있습니다.\n$ git secret reveal File \u0026#39;/home/wanderlust/Ibiza/config.toml\u0026#39; exists. Overwrite? (y/N) y done. all 1 files are revealed. 이제 .secret 파일을 github에 올리면 안전하게 이용할 수 있습니다.\n다른 컴퓨터에서 접속할 때는 어떻게 해야하나요? gpg 알고리즘은 public key로 암호화하여 private key로 복호화합니다. 즉, 우리는 gpg key를 생성한 host에서 다른 host로 private key를 복사해주어야 합니다.\n따라서 다음과 같이 private key를 복사하고, 이를 import합니다.\n# gpg key를 생성했던 host wanderlust@wonderland $ gpg --export-secret-keys my@email.com \u0026gt; private-key.asc # key 복사 wanderlust@wonderland $ scp private-key.asc wanderlust@wonderland-laptop:~/ # gpg key를 import할 host wnaderlust@wonderland-laptop $ gpg --import ~/private-key.asc 그 다음 tell 옵션을 사용합니다.\ngit secret tell my@email.com 마지막으로 reveal 옵션을 통해 복호화합니다.\n$ git secret reveal done. all 1 files are revealed. "},{"uri":"http://kimmj.github.io/harbor/install/","title":"Harbor 설치","tags":["harbor","docker-registry"],"description":"","content":"docker-compose 설치 $ sudo curl -L \u0026#34;https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)\u0026#34; -o /usr/local/bin/docker-compose $ sudo chmod +x /usr/local/bin/docker-compose # 설치 후 docker-compose 명령어가 실패한다면, symbolic link를 직접 걸어주도록 합니다. $ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose $ docker-compose --version docker-compose version 1.24.1, build 1110ad01 harbor installer 다운로드 공식 Github에서 원하는 인스톨러를 다운로드 받습니다. 저는 online installer를 사용할 예정입니다.\n다운로드가 완료되었으면 압축을 해제합니다.\n$ tar xvf harbor-*.tgz harbor/prepare harbor/LICENSE harbor/install.sh harbor/common.sh harbor/harbor.yml harbor.yml 설정 vi로 harbor/harbor.yml을 열고 적당하게 편집합니다.\n여기서는 http로 간단하게 배포하는 설정을 해볼 것입니다.\nhostname: \u0026lt;domain or IP\u0026gt; # 192.168.x.x 그리고 https와 관련된 value를 모두 주석처리해줍니다.\n# https: # https port for harbor, default is 443 # port: 443 # The path of cert and key files for nginx # certificate: /your/certificate/path # private_key: /your/private/key/path proxy 환경이 아니라면 더 손댈 곳은 없습니다. proxy 환경일 경우 하단에 있는 proxy 설정을 /etc/environment 등을 참조하여 미리 적혀있는 부분을 추가하여 작성하시면 됩니다.\ninstall install시 clair, notary, chart-museum을 함께 설치할 수 있습니다.\n여기서 notary는 https 설정이 필요하기 때문에 생략하고 나머지 두개를 설치합니다.\n~/harbor$ ./install.sh --with-clair --with-chartmuseum 확인 이제 hostname에서 설정한 곳으로 접속하여 확인합니다. 설정을 바꾸지 않았다면 80포트로 접속할 수 있기 때문에 address만 입력해주면 접속할 수 있습니다.\n기본 ID/PW는 admin/Harbor12345 입니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/start-tmux-after-reboot/","title":"reboot 후에 tmux를 실행시켜 원하는 작업을 하기","tags":["tmux","reboot","ubuntu"],"description":"","content":"tmux는 terminal을 한 창에 여러개 띄울 때 사용하는 프로그램입니다.\n이 프로그램의 특징은 detach 모드로 들어가면, 어디서든 terminal에 접속하여 해당 session에 접속했을 때, 그 화면 그대로를 가져올 수 있다는 것입니다.\n즉, 원격 접속을 통해 서버에 접속했을 때 작업을 돌려놓고 detach모드로 들어가면 나의 session을 꺼도 실제 서버에서는 해당 작업이 계속해서 돌아가고 있다는 것입니다. 퇴근하기 전 시간이 오래걸리는 작업을 돌려놓고 가야할 때 유용하게 사용할 수 있습니다.\n저의 경우는 제 로컬 컴퓨터에서 hugo를 통해 사이트를 생성하여 블로그를 편집할 때마다 즉시 그 결과를 보고 있습니다. 이 떄 사용되는 명령어는 간단하지만, 매번 컴퓨터를 켤 때마다 이를 수행해주어야 한다는 것은 여간 귀찮은 일이 아닙니다. 따라서 다음과 같은 프로세스로 tmux를 생성하여 제가 하는 작업을 수행하도록 설정할 것입니다.\noverview tmux session을 이름을 지정하여 생성하고, daemon으로 돌린다. 해당 session에 직접 접속하지 않고 명령어를 전달한다. 1, 2의 작업을 스크립트로 만들고 재부팅 시 실행하도록 한다. 절차 sh script 작성 다음과 같은 스크립트를 작성합니다. (tmux-on-reboot.sh)\n#!/bin/zsh SESSIONNAME=\u0026#34;script\u0026#34; tmux has-session -t $SESSIONNAME 2\u0026gt; /dev/null if [ $? != 0 ] then tmux new-session -s $SESSIONNAME -n script -d \u0026#34;bin/zsh\u0026#34; tmux send-keys -t $SESSIONNAME \u0026#34;cd /home/wanderlust/Ibiza\u0026#34; C-m tmux send-keys -t $SESSIONNAME \u0026#34;hugo server --bind 0.0.0.0 --port 8000 --disableFastRender\u0026#34; C-m fi 여기서 C-m은 enter 명령을 주기 위함입니다.\n그 다음 해당 파일에 실행권한을 줍니다.\nchmod +x tmux-on-reboot.sh 재부팅 시 실행하도록 설정 이를 위해서는 crontab을 사용할 것입니다.\ncrontab을 사용하는 방법은 crontab -e를 통해 root 권한으로 명령어를 실행하는 방법과 /etc/crontab을 수정하여 원하는 유저를 부여하는 방법이 있습니다. 여기서는 제가 사용할 계정을 가지고 생성하는 것을 해보도록 하겠습니다.\nvi /etc/crontab vim이 열리면 가장 아랫줄에 다음과 같이 추가합니다.\n@reboot wanderlust /home/wanderlust/scripts/tmux-on-reboot.sh 저장 후 재부팅하여 실행되는지 확인합니다.\nReference https://superuser.com/a/440082? "},{"uri":"http://kimmj.github.io/docker/use-docker-without-sudo/","title":"Docker를 sudo없이 실행하기","tags":["docker","sudo"],"description":"","content":"docker 명령어는 docker group으로 실행됩니다. 그러나 저희가 기존에 사용하던 일반 user는 해당 group에 속하지 않기 때문에 docker 명령어를 쳤을 때 permission에 관한 에러가 발생하게 됩니다.\n이 때 다음과 같이 조치를 하면 sudo 없이 user가 docker 명령어를 사용할 수 있게 됩니다.\nsudo usermod -aG docker $USER session을 다시 열고 docker ps 명령어를 입력하여 에러가 발생하는지 확인합니다.\n$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES "},{"uri":"http://kimmj.github.io/ubuntu/oh-my-zsh-home-end-key/","title":"oh-my-zsh에서 home key와 end key가 안될 때 해결방법","tags":["oh-my-zsh","zsh"],"description":"","content":"oh-my-zsh을 설치하고 원격접속이나 로컬환경에서 터미널에 접속했을 때 home key와 end key가 먹히지 않는 경우가 있습니다.\n이런 경우에 사용하는 terminal에서 home key와 end key를 눌러 실제 어떤 값이 전달되는지 확인한 후, 이를 beginning-of-line, end-of-line으로 설정하면 해결할 수 있습니다.\n해결법 home key가 되지 않는 terminal에 접속합니다. Control+V를 누릅니다. 문제가 되는 home key를 누릅니다. terminal에 뜬 문자를 기록합니다. ~/.zshrc에 다음과 같이 추가합니다. 여기서 case에 관한 부분은 상황에 따라 넣지 않거나 변경해야 합니다. case $TERM in (xterm*) bindkey \u0026#39;^[[H\u0026#39; beginning-of-line bindkey \u0026#39;^[[F\u0026#39; end-of-line esac source ~/.zshrc를 하거나 새로운 session을 열어서 확인합니다. "},{"uri":"http://kimmj.github.io/ubuntu/base64-encode-decode/","title":"Ubuntu에서 Base64로 인코딩, 디코딩하기","tags":["ubuntu","base64"],"description":"","content":"Encode echo로 입력하기 $ echo \u0026#34;password\u0026#34; | base64 cGFzc3dvcmQK Control+D를 누를때까지 입력하기 $ base64 admin password ^D # Control+D # result YWRtaW4KcGFzc3dvcmQK Decode echo로 입력하기 $ echo \u0026#34;cGFzc3dvcmQK\u0026#34; | base64 --decode password Control+D를 누를때까지 입력하기 $ base64 --decode YWRtaW4KcGFzc3dvcmQK ^D # Control+D # result admin password "},{"uri":"http://kimmj.github.io/ubuntu/file-edit-without-editor/","title":"Editor(vi)가 없을 때 파일 수정하기","tags":["ubuntu","file","editor"],"description":"","content":"echo로 파일 내용을 입력하는 방법 \u0026gt;로 파일 덮어쓰기 $ cat file asdfasdfasdf $ echo \u0026#34;asdf\u0026#34; \u0026gt; file $ cat file asdf \u0026gt;\u0026gt;로 파일에 이어쓰기 $ cat file asdf $ echo \u0026#34;asdf\u0026#34; \u0026gt;\u0026gt; file $ cat file asdf asdf cat으로 파일 입력하는 방법 \u0026gt;로 파일 덮어쓰기 $ cat file asdf $ cat \u0026gt; file aaaa bbbb ^D # Command+D $ cat file aaaa bbbb \u0026gt;\u0026gt;로 파일에 이어쓰기 $ cat file asdf $ cat \u0026gt;\u0026gt; file aaaa bbbb ^D # Command+D $ cat file asdf aaaa bbbb \u0026laquo;EOF로 EOF을 입력하면 입력 완료하기 $ cat file asdf $ cat \u0026lt;\u0026lt;EOF \u0026gt; file aaaa bbbb EOF $ cat file asdf aaaa bbbb "},{"uri":"http://kimmj.github.io/kubernetes/stern/","title":"Stern을 이용하여 여러 pod의 log를 한번에 확인하기","tags":["kubernetes","stern","log"],"description":"","content":"Kubernetes에서의 trouble shooting kubernetes 환경에서 어떤 문제가 발생하면 다음과 같은 flow로 확인을 해보면 됩니다.\n kubectl get pods -o yaml로 yaml을 확인하기 kubectl describe pods로 pod에 대한 설명 확인하기 kubectl describe deployments(statefulset, daemonset)으로 확인하기 kubectl logs로 로그 확인하기 보통 kubernetes 리소스의 부족과 같은 kubernetes단의 문제는 1~3을 확인하면 전부 문제점을 찾을 수 있습니다. 그러나 어플리케이션의 직접적인 원인을 알아보기 위해서는 log를 확인해야 합니다.\n하지만 kubectl의 logs에는 한가지 한계점이 있는데, 바로 단일 container에 대해서만 log 확인이 가능하다는 점입니다.\nstern은 이러한 문제를 해결하는 tool입니다.\n설치 방법 공식 Github: https://github.com/wercker/stern\nRequirements golang govendor 기타 build에 필요한 dependency는 govendor로 관리 절차 golang 설치하기 go 언어는 직접 github에서 가져와 build할 수도 있지만, 간단하게 Ubuntu라면 apt를, macOS라면 brew를 이용하여 설치할 수 있습니다.\n# Ubuntu sudo apt install golang # macOS brew install go govendor 설치하기 govendor는 external package를 import하는데 사용하는 툴입니다. 이를 통해 dependency를 쉽게 import할 수 있습니다.\n간단하게 go 명령어만으로 설치할 수 있습니다.\n# install $ go get -u github.com/kardianos/govendor # test $ govendor --version v1.0.9 만약 govendor 명령어가 되지 않는다면, 보통은 go의 binary 폴더가 PATH에 추가되지 않은 것입니다.\ngo env를 통해 GOPATH, GOBIN을 확인합니다. GOBIN이 비어있을 경우 $GOPATH/bin이 GOBIN입니다.\n정상적으로 binary 파일이 있다면 PATH에 추가합니다. 저는 GOPATH가 /root/go였으므로 다음과 같이 입력하였습니다. terminal에 입력하는 것은 임시조치이므로 영구적으로 기록하려면 /etc/profile 또는 ~/.bashrc같은 파일에 저장합니다.\nexport PATH=$PATH:/root/go/bin stern 설치하기 env | grep GOPATH 명령어를 통해 GOPATH가 제대로 적용이 되는지 먼저 확인합니다. 제대로 안되어있을 경우 export GOPATH=/root/go처럼 적용해줍니다. 그 다음 다음의 명령어를 입력합니다.\nmkdir -p $GOPATH/src/github.com/wercker cd $GOPATH/src/github.com/wercker git clone https://github.com/wercker/stern.git \u0026amp;\u0026amp; cd stern govendor sync go install 확인 $ stern -v stern version 1.11.0 사용법 정확한 사용법은 이곳을 확인하시면 좋습니다.\n간단하게 설명하면, stern은 regEx로 매칭되는 pod나 container의 log를 tail -f처럼 보여주는 것입니다. 또한 그 결과는 색깔로 구분지어 보기 쉽습니다.\nstern -n kube-system kube-proxy-* "},{"uri":"http://kimmj.github.io/git/gitignore/","title":"Gitignore 설정","tags":["git","gitignore"],"description":"","content":"참조: https://git-scm.com/docs/gitignore\ngitignore은 git에서 어떤 파일을 무시할지 설정하는 파일입니다. 이미 tracked된 것들에는 영향을 주지 않습니다.\ngitignore 참조 순서 syntax blank line 파일과 매칭되지 않습니다. 따라서 가독성을 위해 사용할 수 있습니다.\n# #은 comment로 처리됩니다.\nTraling space \\로 감싸졌다고 하더라도 무시됩니다.\n! !는 not과 같습니다. 파일 이름 맨 앞에 !가 있고, 이를 ignore할 때 사용하려면 \\를 통해 escape 해줘야 합니다.\n/ /는 디렉토리를 구분합니다. 처음, 중간, 끝 어느 위치에도 올 수 있습니다.\n끝에 /를 사용하면 매칭되는 폴더에만 적용이 됩니다. 사용하지 않으면 매칭되는 폴더, 파일 모두 적용됩니다.\n* /를 제외한 모든 길이의 문자와 매칭됩니다.\n? /를 제외한 한 문자와 매칭됩니다.\nrange [a-zA-Z]처럼 range를 설정할 수 있습니다.\n** **는 /를 포함한 모든 길이의 문자와 매칭됩니다.\n**로 시작하고 /를 적으면 모든 디렉토리에서 검색합니다. 예를 들어 **/foo는 모든 디렉토리에 있는 foo와 매칭됩니다. (현재 디렉토리인 .도 포함됩니다. ..는 포함되지 않습니다.)\n**로 끝나는 경우 모든 내부 파일, 폴더와 매칭됩니다. 예를 들어 abc/**는 abc 폴더 내부의 모든 파일, 폴더와 매칭됩니다.\n**를 중간에 적은경우 모든 sub directory(0개 이상)와 매칭됩니다. 예를 들어 a/**/b는 a/b, a/x/b, a/x/y/b 모두 매칭됩니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/check-listen-port/","title":"열려있는 포트 확인하기","tags":["linux","port","network"],"description":"","content":"열려있는 포트 확인하기 # 방법 1 lsof -i -nP | grep LISTEN | awk \u0026#39;{print $(NF-1)\u0026#34; \u0026#34; $1}\u0026#39; | sort -u # 방법 2 netstat -tnlp 열려있는 포트 확인하기 + 관련된 프로세스 이름 확인하기 netstat -tnlp | grep -v 127.0.0.1 | sed \u0026#39;s/:::/0 /g\u0026#39; | sed \u0026#39;s/[:\\/]/ /g\u0026#39; | awk \u0026#39;{print $5\u0026#34;\\t\u0026#34;$10}\u0026#39; | sort -ug "},{"uri":"http://kimmj.github.io/ubuntu/using-watch-with-pipes/","title":"pipe를 사용한 명령어를 watch로 확인하기","tags":["watch","pipe"],"description":"","content":"pipe(|)는 grep과 다른 기타 명령어들과 함께 사용하면 좀 더 다양한 작업을 할 수 있습니다.\nwatch는 특정 명령어를 주기적으로 입력하여 결과 메시지를 확인합니다. 즉, 무엇인가를 모니터링할 때 주로 사용하곤 합니다.\n바로 본론으로 들어가서 pipe를 사용한 명령어를 watch로 확인하는 방법은 다음과 같습니다.\nwatch \u0026#39;\u0026lt;command\u0026gt;\u0026#39; 위와같이 quote로 감싸주세요.\nls -al을 가지고 확인해 보도록 하겠습니다.\n$ ls -al | grep config -rw-rw-r-- 1 wanderlust wanderlust 2.9K 1월 21 23:40 config.toml $ watch ls -al | grep config # quote를 사용하지 않은 것 ^C # 결과 출력되지 않음 $ watch \u0026#34;ls -al | grep config\u0026#34; Every 2.0s: ls -al | grep config -rw-rw-r-- 1 wanderlust wanderlust 2960 1월 21 23:40 config.toml $ watch \u0026#34;ll | grep config\u0026#34; Every 2.0s: ll | grep config sh: 1: ll: not found 특히 마지막을 보시면 alias된 명령어는 인식하지 못하는 것을 알 수 있습니다.\n다른 페이지에서 했던 alias watch=\u0026quot;watch \u0026quot;를 적용했다고 하더라도, alias된 것을 quote로 감쌌을 때에는 인식하지 못하는 것을 볼 수 있습니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/use-alias-in-watch/","title":"watch를 사용할 때 alias 이용하기","tags":["watch","ubuntu","alias"],"description":"","content":"watch는 정해진 시간동안 뒤에 적은 명령어를 실행해주는 프로그램입니다. 가령 kubernetes를 다룰 때 watch kubectl get pods -n kube-system을 통해 kube-system 네임스페이스에 있는 파드들을 지속적으로 모니터링 할 수 있습니다.\n그러나 watch는 alias된 명령어를 인식하지 못합니다.\n$ ll total 44K drwxrwxr-x 2 wanderlust wanderlust 4.0K 1월 7 20:38 archetypes -rw-rw-r-- 1 wanderlust wanderlust 2.9K 1월 21 23:40 config.toml drwxrwxr-x 16 wanderlust wanderlust 4.0K 2월 22 23:08 content $ watch ll Every 2.0s: ll sh: 1: ll: not found 이 때 해결할 수 있는 가장 편한 방법은 watch 자체를 alias 시켜버리는 것입니다.\n저는 zsh을 사용하고 있으므로 ~/.zshrc에 다음과 같이 추가하였습니다.\nalias watch=\u0026#39;watch \u0026#39; 그 다음 설정파일을 다시 불러오거나 새로운 session을 생성합니다.\n# 설정파일 다시 불러오기 source ~/.zshrc 다시 watch를 하여 확인해 봅니다.\n$ watch ll Every 2.0s: ls --color=tty -lh total 44K drwxrwxr-x 2 wanderlust wanderlust 4.0K 1월 7 20:38 archetypes -rw-rw-r-- 1 wanderlust wanderlust 2.9K 1월 21 23:40 config.toml drwxrwxr-x 16 wanderlust wanderlust 4.0K 2월 22 23:08 content 정상적으로 alias된 명령어를 인식하게 되었습니다.\n"},{"uri":"http://kimmj.github.io/python/python-beautiful-cli/","title":"[번역]Python을 통해 이쁜 CLI 만들기","tags":["python"],"description":"","content":"링크 : https://codeburst.io/building-beautiful-command-line-interfaces-with-python-26c7e1bb54df\ncommand line application을 만드는 것을 다루기 전에 빠르게 Command Line에 대해서 알아보자.\ncommand line 프로그램은 컴퓨터 프로그램이 생성되었을 때부터 우리와 함께 해왔고, 명령어들로 구성되어있다. commnad line 프로그램은 command line에서 또는 shell에서 동작하는 프로그램이다.\ncommand line interface는 user interface이지만 마우스를 사용하는 것이 아닌 terminal, shell, console에서 명령어를 입력하여 사용하는 것이다. console은 이미지나 GUI가 하나도 없이 전체 모니터 스크린이 텍스트로만 이루어진 것을 의미한다.\n위키피디아에 의하면\n CLI는 주로 1960년대 중만에 컴퓨터 terminal에서의 대부분의 컴퓨터 시스템과의 상호작용을 의미하고 1970년대와 1980년대를 거쳐 OpenVMS, MS-DOS를 포함한 개인용 컴퓨터와 Unix system, CP/M과 Apple DOS에서 사용되어 왔다. 인터페이스는 보통 명령어를 텍스트로 받고 이 명령어를 통해 적절한 system function을 동작시키게 하는 command line shell에서 동작한다.\n 왜 Python인가? Python은 유연성과 현존하는 프로그램들과도 잘 작동하기 때문에 보통 glue code language라고 여겨진다. 대부분의 Python 코드는 script와 command-line interface(CLI)로 작성된다.\n이런 command-line interface와 tool은 거의 대부분 원하는 것들을 자동화할 수 있기 때문에 특히 강력하다.\n우리는 예쁘고 상호작용을 하는 인터페이스, UI, UX가 매우 중요한 시대를 살고 있다. 우리는 이런 것들을 Command Line에 추가하고 사람들이 이를 받아들이고 Heroku같은 유명한 회사는 이를 공식적으로 사용할 것이다.\narguments와 option을 파싱하는 것에서부터 output에 색깔을 주고, progress bar를 추가하고 email을 전송하는 것과 같은 엄청난 CLI \u0026ldquo;frameworks\u0026quot;를 flagging하기까지 command line app을 build하는데 도움을 주는 많은 Python library와 module이 있다.\n이런 module과 함께 우리는 Heroku나 Vue-init이나 NPM-init같은 Node programe처럼 예쁘고 상호작용을 하는 command line interface를 만들 수 있다.\n예쁜 vue init CLI를 쉽게 만드려면 Inquirer.js를 Python에 이식하는 Python-inquirer를 사용한는 것을 권장한다.\n불행히도 Python-inquirer는 blessings(Unix같은 시스템에서만 사용가능한 _curses와 fcntl module을 import하는 python package)를 사용하기 때문에 Windows에서 작동하지 않는다. 어떤 대단한 개발자들은 _curses를 Windows에 이식할 수도 있긴 할것이다. Windows에서 fcntl의 대체제는 win32api이다.\n하지만 구글링을 통해 나는 이를 고치게 되었고 이를 PyInquirer라고 불르게 되었다. 이는 python-inquirer의 대체제이고 더 좋은 점은 이것은 Windows를 포함한 모든 플랫폼에서도 사용이 가능하다는 것이다.\nBasics in Commnad Line Interface with Python 이제 간단하게 command line interface를 보고 Python으로 하나를 만들어보자.\ncommand-line interface(CLI)는 실행파일의 이름으로 보통 시작한다. 그냥 console에서 이름을 입력하면 pip처럼 스크립트의 main entry point에 접근하게 된다.\nscript가 어떻게 개발되었는지에 따라 우리가 전달해 주어야 할 parameter들이 있고 이들은 이런 종류가 있다.\n Arguments: 스크립트에 전달되어야 하는 required parameter이다. 이를 작성하지 않으면 CLI는 error를 발생시킬 것이다. 예를 들어 django는 여기서 arguments이다. pip install django Options: 이름에서 알 수 있듯이 optional parameter이다. 보통은 pip install django --cache-dir ./my-cache-dir처럼 name과 value의 쌍으로 사용한다. --cache-dir은 option parameter이고 value ./my-cache-dir은 cache directory로 사용되는 것이다. Flags: script에게 특정 행동을 disable할지 enable할지 알려주는 특수한 option parameter이다. 대부분은 --help같은 것들이다. Heroku Toolbelt같은 복잡한 CLI를 통해 우리는 main entry point 아래의 몇몇 command들에 접근할 수 있게 된다. 이를 보통 commands와 sub-commands라고 한다.\n다른 python package들로 똑똑하고 예쁜 CLI를 어떻게 빌드하는지 보자.\nArgparse argparse는 command line program을 생성하는 default python module이다. 간단한 CLI를 만드는 데에 필요한 모든 기능을 제공한다.\nimport argparse parser = argparse.ArgumentParser(description=\u0026#39;Add som integers.\u0026#39;) parser.add_argument(\u0026#39;integers\u0026#39;, metavar=\u0026#39;N\u0026#39;, type=int, nargs=\u0026#39;+\u0026#39;, helm \u0026#39;integer list\u0026#39;) parser.add_argument(\u0026#39;--sum\u0026#39;, action=\u0026#39;store_const\u0026#39;, const=sum, default=max, help=\u0026#39;sum the integers (default: find the max)\u0026#39;) args = parser.parse_args() print(args.sum(args.integers)) argparse는 간단한 추가적인 동작을 한다. argparse.ArgumentParser는 프로그램에 description을 추가할 수 있도록 하는 반면 parser.add_argument는 command를 추가할 수 있도록 한다. parser.parse_args()는 주어진 arguments를 리턴하고 보통 이것들은 name-value 쌍으로 제공된다.\n예를 들어 args.integers를 통해 integers arguments에 접근할 수 있다. 위의 script에서 --sum은 optional argument이지만 N은 positional argument이다.\nClick click으로 우리는 CLI를 argparse보다는 쉽게 만들 수 있다. click은 argparse가 해결하는 문제와 동일한 것을 해결해준다. 하지만 약간은 다른 접근방식을 사용한다. 이는 decorators concept을 사용한다. 이는 command가 function이 되도록 하며 이를 decorators로 감쌀 수 있게 한다.\n# cli.py import click @click.command() def main(): click.echo(\u0026#34;This is a CLI built with Click\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() 아래와 같이 argument와 option을 추가할 수 있다.\n# cli.py import click @click.command() @click.argument(\u0026#39;name) @click.option(\u0026#39;--greeting\u0026#39;, \u0026#39;-g\u0026#39;) def main(name, greeting): click.echo(\u0026#34;(), ()\u0026#34;.format(greeting, name)) if __main__ == \u0026#34;__main__\u0026#34;: main() 위의 script를 실행해보면 다음과 같을 것이다.\n$ python cli.py --greeting \u0026lt;greeting\u0026gt; Oyetoke Hey. OyeTOKI 모든걸 모아 Google Books의 query books에 간단한 CLI를 만들었다.\n click에 대한 자세한 정보는 official documentation에서 확인할 수 있다.\nDocopt docopt는 POSIC-style이나 Markdown 사용법을 파싱하여 쉽게 command line interface를 생성하는 lightweight python package이다. docopt는 수년간 사용해온 command line interface를 설명하는 정형화된 helm message와 man page에 대한 convention을 사용한다. docopt에서의 interface description은 helm message와 비슷하지만 정형화되어있다.\ndocopt는 파일의 젤 위에 required docstring이 어떻게 형식을 갖추는지가 중요하다. tool의 이름 다음에 올 docstring에서의 맨 위의 element는 Usage이여야 하고 이는 command가 어떻게 호출되고자 하는지에 대한 리스트들을 나열해야 한다.\ndocstring에서 두번째 element는 Options여야 하고 이는 Usage에서 나타난 option과 arguments에 대한 정보를 제공해야 한다. docstring의 내용들은 help text의 내용이 될 것이다.\n PyInquirer PyInquirer는 interactive command line user interface이다. 우리가 위에서 보았던 package들은 우리가 원하는 \u0026ldquo;예쁜 interface\u0026quot;를 제공하지 않는다. 따라서 어떻게 PyInquirer를 사용하는지 알아보도록 하자.\nInquirer.js처럼 PyInquirer는 두가지 간단한 단계로 구성되어 있다.\n 질문 리스트를 정의하고 이를 prompt에 전달한다. prompt는 답변 리스트를 리턴한다. from __future__ import print_function, unicode_literals from PyInquirer import prompt from pprint import pprint questions = [ { \u0026#39;type\u0026#39;: \u0026#39;input\u0026#39;, \u0026#39;name\u0026#39;: \u0026#39;first_name\u0026#39;, \u0026#39;message\u0026#39;: \u0026#39;What\\\u0026#39;s your first name\u0026#39;, } ] answers = prompt(questions) pprint (answers) 상호작용하는 예시이다.\n 결과는 다음과 같다.\n이 스크립트의 몇몇 부분을 보도록 하자.\nstyle = style_from_dict({ Token.Separator: \u0026#39;#cc5454\u0026#39;, Token.QuestionMark: \u0026#39;#673ab7 bold\u0026#39;, Token.Selected: \u0026#39;#cc5454\u0026#39;, # default Token.Pointer: \u0026#39;#673ab7 bold\u0026#39;, Token.Instruction: \u0026#39;\u0026#39;, # default Token.Answer: \u0026#39;#f44336 bold\u0026#39;, Token.Question: \u0026#39;\u0026#39;, }) style_from_dict는 우리의 interface에 적용하고자 하는 custom style을 정의할 때 사용한다. Token은 component와 같은 것이고 이 아래에는 다른 component들도 있다.\n이전 예시에서 questions list를 prompt로 전달하여 처리하는 것을 보았다.\n이 방식으로 우리가 생성할 수 있는 interactive CLI에는 이런 예시가 있다.\n 결과\nPyFiglet pyfiglet은 ASCII text를 arts fonts로 변환해주는 python module이다. pyfiglet은 모든 FIGlet(http://www.figlet.org/)들을 pure python에 이식한 것이다.\nfrom pyfiglet import Figlet f = Figlet(font=\u0026#39;slant\u0026#39;) print f.rederText(\u0026#39;text to render\u0026#39;) 결과\nClint clint는 CLI를 만드는데 필요한 모든 것들을 통합한 것이다. color도 지원하고 뛰어난 nest-able indentation context manager도 지원하며 custom email-style quotes, auto-expanding column이 되는 column printer도 지원한다.\n EmailCLI 모든걸 통합하여 나는 SendGrid를 통해 메일을 전송하는 간단한 cli를 작성하였다. 아래의 스크립트를 사용하려면 SendGrid에서 API Key를 받아야 한다.\nInstallation pip install sendgrid click PyInquirer pyfiglet pyconfigstore colorama termcolor six 읽으면 좋은 것: https://www.davidfischer.name/2017/01/python-command-line-apps/ "},{"uri":"http://kimmj.github.io/docker/connect-container-to-container/","title":"[docker-compose] container에서 다른 container로 접속하기","tags":["docker","docker-compose","network","bridge","container"],"description":"","content":"배경 docker-compose에서는 network bridge를 설정합니다. 이 bridge로 내부 통신을 하게 되죠. 여기서 port-forward를 통해 외부로 서비스를 expose하게 되면 host의 IP와 port의 조합으로 접속할 수 있습니다.\n그런데 저는 네트워크 설정의 문제인지, 하나의 container에서 host IP로 접속이 불가능했습니다. 그러면서도 저는 어떻게든 다른 docker-compose의 서비스로 네트워킹이 됐어야 했습니다. 정확히 말하자면 harbor라는 서비스(docker registry)에서 jenkins로 webhook을 날려야 하는 상황이었죠.\n먼저 시도했던 것은 jenkins의 ip를 docker inspect jenkins_jenkins_1을 통해 알아내고, 이를 통해 webhook을 전송하는 것이었습니다. 그러나 실패했죠.\n다음으로 생각해본 것은, 그렇다면 jenkins를 harbor의 bridge로 연결해보자는 것이었습니다.\n따라서 다음과 같은 조치를 취해주었습니다.\ncontainer에서 다른 container로 접속하기 이미 동작중인 container에 bridge 연결 docker network connect harbor_harbor jenkins_jenkins_1 # docker network connect \u0026lt;bridge\u0026gt; \u0026lt;container\u0026gt; 연결된 bridge에서의 ip 확인 $ docker inspect jenkins_jenkins_1 \u0026#34;harbor_harbor\u0026#34;: { \u0026#34;IPAMConfig\u0026#34;: {}, \u0026#34;Links\u0026#34;: null, \u0026#34;Aliases\u0026#34;: [ \u0026#34;---\u0026#34; ], \u0026#34;NetworkID\u0026#34;: \u0026#34;---\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;---\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;172.24.0.1\u0026#34;, \u0026#34;IPAddress\u0026#34;: \u0026#34;172.24.0.11\u0026#34;, # \u0026lt;--------IP 연결 확인 harbor-core $ curl 172.24.0.11:8080 이 때, 연결은 host가 port-forward한 port가 아닌, container가 expose하고 있는 port를 입력해주어야 합니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/ssh-without-password/","title":"password 없이 ssh 접속하기","tags":["ssh","passwordless"],"description":"","content":"자주 접속하는 서버에 패스워드를 항상 입력하는 것은 귀찮은 일이 될 것입니다.\n여기에서는 ssh key를 생성하고, 이를 이용하여 인증을 해 password를 입력하지 않는 방법을 알아볼 것입니다.\nssh-keygen을 통한 ssh key 생성 ssh 접속을 할 때 password를 입력했던 것처럼, 항상 ssh 접속을 위해서는 인증을 위한 key가 필요합니다.\n인증에 사용할 키를 ssh-keygen으로 생성하는 방법은 다음과 같습니다.\nssh-keygen -t rsa -b 4096 -t는 rsa 알고리즘을 통해 key를 생성하겠다는 의미이며, -b는 key의 사이즈를 정해주는 것입니다.\n다른 알고리즘들과 다른 옵션들은 https://www.ssh.com/ssh/keygen에서 더 확인할 수 있습니다.\n이제 ssh 인증을 위한 public key를 생성하였습니다.\nssh-copy-id를 통한 public key 복사 위에서 생성한 public key를 접속하고자 하는 서버에 복사를 하면, 서버에 접속할 때 서버는 해당 파일을 참조하여 인증을 시도할 것입니다.\nssh-copy-id user@server 이 때 최초 1회만 패스워드를 입력하면 됩니다. 그 뒤 다시한번 로그인을 시도해보면 패스워드 없이 로그인에 성공할 것입니다.\n "},{"uri":"http://kimmj.github.io/ubuntu/ssh-tunneling/","title":"SSH Tunneling 사용법","tags":["ubuntu","tunneling"],"description":"","content":"-D 옵션으로 socks proxy 사용하기 A라는 서버에서 B라는 서버에 있는 서비스를 보려고 합니다. 이 때, 해당 웹 어플리케이션은 B에서만 연결된 특정 IP로 통신을 하고 있고, 이 때문에 A에서 어플케이션이 제대로 동작하지 않는 상황입니다.\n이 때 사용할 수 있는 것이 -D 옵션입니다.\n예시\nssh -D 12345 user@server.com 해당 세션이 꺼져있지 않은 상태에서 A 서버에서 웹 브라우저가 localhost:12345를 프록시로 사용하도록 하면 해당 웹 어플리케이션이 제대로 동작합니다.\n만약 windows라면 다음과 같이 진행하면 socks proxy를 사용하도록 할 수 있습니다. CMD를 열고 다음과 같이 입력하면 새로운 창으로 chrome이 뜰 것입니다.\n\u0026#34;C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\u0026#34; --user-data-dir=\u0026#34;%USERPROFILE%\\proxy-profile\u0026#34; --proxy-server=\u0026#34;socks5://localhost:12345\u0026#34; 해당 창에서 어플리케이션을 실행하면 실제 B 서버의 desktop에서 동작하는 것과 같은 효과를 볼 수 있습니다.\n-L 옵션으로 특정 포트로 접속하기 A에서 B라는 서버의 어플리케이션을 사용하려 합니다. 이 어플리케이션은 특정 포트를 사용하고 있습니다. 쉬운 예시를 위해 jenkins를 B에서 구동한다고 하고 포트를 8080이라고 하겠습니다.\n이 상황에서 A에서는 server-b.com:8080에 접속할 수 없는 상황입니다.\n이 경우 -L 옵션을 사용하면 됩니다.\nssh -L 1234:localhost:8080 user@server-b.com 해당 설정을 읽어보자면, A서버의 localhost:1234로 들어오는 요청들을 user@server-b.com으로 접속한 뒤 localhost:8080으로 보내라는 의미입니다.\n다음과 같은 설정도 있을 수 있습니다.\nssh -L 12345:server-c.com:8080 user@server-b.com 이 설정은 A에서 C로 직접 통신이 안되지만, B에서 C로 통신이 되는 상황입니다.\n이 때, 위의 설정은 localhost:12345로 들어오는 요청들을 user@server-b.com으로 접속한 뒤 server-c.com:8080으로 보내라는 의미입니다.\n-R 옵션을 이용하여 Reverse Proxy 사용하기 A에서 B로 ssh 접속을 하고 있습니다. 이 때, B에서 A로는 접속이 방화벽으로 막혀있는 상황입니다.\n이런 상황에서 -R 옵션을 주면 B에서도 A로 접속할 수 있습니다.\nssh -R 12345:localhost:22 user@server-b.com 해당 설정은 A서버에서 접속하는 localhost의 22 포트를 B서버의 12345 포트와 연결하라는 의미입니다.\n그 다음 해당 세션이 유지된 상태에서 B에서 다음과 같이 접속합니다.\nssh localhost -p 12345 그러면 B서버에서 A서버로 접속이 가능한 것을 볼 수 있습니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/ssh-with-jump/","title":"Gateway를 이용하여 SSH 접속하기","tags":["ssh","linux"],"description":"","content":"ssh cli 이용하는 방법 -J 옵션을 이용한다.\nssh user@server -J user2@server2 두개 이상의 경우 ,로 구분한다.\n예: user2@server2로 접속 후 user3@server3로 접속한 뒤 user@server로 접속해야 할 경우\nssh user@server -J user2@server2,user3@server3 이 상황에서 ssh-copy-id를 이용해 패스워드를 입력하지 않고 이동하려면\nlocaluser@localhost $ ssh-copy-id user2@server2 localuser@localhost $ ssh user2@server2 user2@server2 $ ssh-copy-id user3@server3 user2@server2 $ ssh user3@server3 user3@server3 $ ssh-copy-id user@server 이후 ssh를 통해 진입하면 패스워드 없이 접속 가능.\n만약 port가 필요한 경우 server:port 형태로 입력\nssh user@server:port -J user2@server2:port2,user3@server3:port3 ssh config 파일 이용하는 방법 Host server HostName remote-server User user ProxyJump gateway2 Host server2 HostName gateway1 User user2 Host server3 HostName gateway2 User user3 ProxyJump gateway1 "},{"uri":"http://kimmj.github.io/iac/translate-what-is-infrastructure-as-a-code/","title":"[번역] What Is Infrastructure as a Code? How It Works, Best Practices, Tutorials","tags":["IaC","infrastructure-as-code"],"description":"","content":"link: https://stackify.com/what-is-infrastructure-as-code-how-it-works-best-practices-tutorials/\n과거에 IT infrastructure를 관리하는 것은 힘든 일이었다. 시스템 관리자는 수동으로 관리하고 어플리케이션을 구동시키기 위해 모든 하드웨어와 소프트웨어를 설정해야 했다.\n하지만 최근 몇년간 급격하게 상황들이 바뀌었다. cloud computing같은 트렌드가 디자인, 개발, IT infrastructure의 유지를 하는 방법을 혁명화하고 발전시켰다.\n이러한 트렌드의 핵심 요소는 infrastructure as code이다. 여기에 대해 이야기 해보도록 하겠다.\nDefining Infrastructure as Code infrastructure를 코드로 정의하는 것부터 시작해보도록 하자. 이것이 무엇을 의미하는지, 어떤 문제들을 해결하는지 배우게 될 것이다.\nWikipedia에서 IaC는 다음과 같이 정의되어 있다.\n Infrastructure as code is the process of managing and provisioning computer data centers through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.\n Infrastructure as code는 물리적인 하드웨어 설정이나 상호작용을 하는 설정 툴을 이용하는 것이 아닌, 기계가 읽을 수 있는 파일로 정의하여 computer data center들을 관리하고 프로비저닝하는 프로세스이다.\n 이 정의는 나쁘지 않지만 약간 너무 장황하게 설명했다. 더 간단하게 해보자.\n Infrastructure as code (IaC)는 IT infrastructure를 설정 파일로 관리한다는 의미이다.\n 다음 질문은 이렇게 될 것이다. \u0026ldquo;왜 그걸 쓰고싶어 하나요?\u0026rdquo;\nWhat Problem Does IaC Solve? \u0026ldquo;what\u0026quot;에 대해 생각하기 전에 \u0026ldquo;why\u0026quot;에 먼저 집중해보자. 왜 IaC가 필요한가? 이것이 어떤 문제를 풀어주는가?\nThe Pain of Managing IT Infrastructure 역사적으로 IT infrastructure를 관리하는 것은 수동 프로세스였다. 사람들은 물리적으로 서버를 위치시키고 이를 설정했다. 머신이 OS와 어플리케이션이 원하는 설정으로 되었을 때에, 어플리케이션을 배포할 수 있게 된다. 당연하게도 수동 프로세스는 자주 문제를 일으킨다.\n첫번째 큰 문제는 비용이다. 네트워크 엔지니어에서부터 하드웨어 관리 기술자까지 많은 전문가를 고용해서 프로세스의 각 단계를 수행시키도록 해야한다. 이런 모든 사람에게 돈을 지불해야 하면서 또한 관리되어야 한다. 이는 관리 오버헤드를 야기하고 기업 내의 소통의 복잡성을 증대시킨다. 결과적으로? 돈이 사라진다. 또한 우린 아직 비용을 더 많이 늘리는 데이터센터를 관리하는 것과 빌딩에 대한 이야기를 하지 않았다.\n그 다음 문제는 확장성과 가용성이다. 하지만 결국 모든 것은 속도 문제다. 수동으로 설정하는 것은 너무 느리고, 어플리케이션의 접속은 스파이크를 치고 있는 동안 시스템 관리자는 부하를 관리하기 위해 필사적으로 서버를 세팅할 것이다. 이는 가용성에도 무조건 영향을 미친다. 기업이 백업 서버나 심지어 데이터 센터가 없다면 어플리케이션은 장기간동안 이용불가능해질 것이다.\n마지막으로 가장 중요한 문제는 inconsistency이다. 몇몇 사람들이 수동으로 설정을 배포한다면, 균열은 생길수밖에 없다.\nCloud Computing: A Cure? Cloud computing은 방금 읽었던 고통들을 완화시켜준다. 이는 데이터센터를 구축하고 유지보수하는 것과 그 비용으로부터 자유롭게 해준다.\n그러나 Cloud computing은 만병통치약과는 거리가 멀다. 이것이 infrastructure를 빠르게 셋업하는데에는 도움을 주겠지만 (그래서 고가용성이나 확장성의 문제는 해결해 준다) 이는 inconsistency 이슈를 해결하지는 못한다. 설정을 수행하는 사람이 한명보다 많다면, 불균형이 생길 수 있다.\nInfrastructure as Code: The Missing Piece of the Puzzle IaC 정의를 다시 한번 봐보자.\n Infrastructure as code (IaC)는 IT infrastructure를 설정 파일로 관리한다는 의미이다.\n 정의에서 가져온 핵심은 바로 다음과 같다. IaC 이전에 IT 사람들은 infrastructure에 대해 수동으로 설정을 바꾸어야 했다. 아마 스크립트를 쓰거나 몇몇 작업은 자동화를 시켰겠지만, 그냥 그 정도였다. IaC를 통해 infrastructure의 설정은 코드 파일의 형태를 띄게 되었다. 이는 단순한 텍스트이지만 수정하고 복사하고 분배하는 것이 쉽다. 당신은 다른 소스코드 파일들처럼 source control로 이를 관리할 수 있고 또 그래야 한다.\nInfrastructure as Code Benefits 이제까지 수동으로 infrastructure를 관리하는 것의 문제점들을 알아 보았다. 우리는 여기서 어떻게 cloud computing이 이런 몇가지 문제점을 해결해 주는지, 또 어떤것들은 해결해주지 않는지 알아보았다. 그리고 우리는 IaC가 퍼즐의 마지막 조각이라고 말하며 이 논의를 마쳤다.\n이제 우리는 IaC solution을 사용할 때의 이점에 대해서 알아볼 것이다.\nSpeed IaC의 가장 중요한 이점은 스피드이다. Infrastructure as Code는 script를 실행시켜 빠르게 완전한 infrastructure를 셋업할 수 있게 해준다. 이를 개발환경과 production 환경부터 staging, QA 등등까지 모든 환경에 대해서 할 수 있다. IaC는 전체 소프트웨어 개발 라이프사이클을 효과적으로 만들어준다.\nConsistency 수동 프로세스는 실수를 불어일으키고 시간이 걸린다. 사람은 실수할 수 있다. 우리의 기억은 잘못될 수 있다. 소통은 어렵고 우리는 일반적으로 소통을 어려워한다. 여기서 읽었던 것 처럼 수동으로 인프라를 관리하는 것은 얼마나 열심히 하던지 간에 불균형을 일으키게 된다. IaC는 믿을 수 있는 하나의 소스코드로 이 설정 파일을 관리하여 이를 해결해준다. 이런 방식으로 동일한 설정이 어떤 불균형도 없이 계속해서 배포됨을 보장한다.\nAccountability 이는 빠르고 쉬운 것이다. IaC 설정 파일을 소스 코드 파일처럼 버전화 할 수 있기 때문에 설정들의 변경사항을 추적할 수 있다. 누가 이를 했고 언제 했는지 추측할 필요가 없다.\nMore Efficiency During the Whole Software Development Cycle infrastrucutre as code를 적용하면 infrastructure 아키텍쳐를 어려 단계로 배포할 수 있다. 이는 전체 소프트웨어 개발 라이프 사이클을 더욱 효율적으로 해주어 팀의 생산성을 더 끌어올릴 수 있다.\n프로그래머들이 IaC를 사용하여 sandbox 환경을 생성하여 독립된 공간에서 안전하게 개발할 수 있게 할 수 있다. 같은 방식으로 테스트를 돌리기 위해 production 환경을 복사해야 하는 QA 전문가들도 사용할 수 있다. 결과적으로 배포 시에 infrastructure as code는 하나의 단계가 될 것이다.\nLower Cost IaC의 주된 장점 중 하나는 의심할 여지 없이 인프라 관리의 비용이 줄어든다는 것이다. IaC를 통해 cloud computing을 하면 극적으로 비용을 줄일 수 있다. 이는 하드웨어에 많은 시간을 사용하지 않아도 됨을 의미하고 이를 관리할 사람을 고용하지 않아도 되며 저장할 물리적인 장소를 만들거나 대여하지 않아도 됨을 의미한다. 하지만 IaC는 기회비용이라 부르는 다른 미묘한 방식으로 비용을 더 줄여준다.\n보았듯이 똑똑하고, 높은 급료를 받는 전문가가 자동화 할 수 있는 작업을 수행하는 것은 돈 낭비이다. 이제 모든 포커스는 기업에 더 가치있는 일에 맞춰져야 한다. 그리고 자동화 전략을 사용하면 편하다. 이를 사용하여 수동적이고 느리고 에러가 나기 쉬운 작업을 수행하는 것으로부터 엔지니어들을 해방시켜주고 더 중요한 일에 집중할 수 있게 한다.\nHow Does IaC Work? IaC 툴은 어떻게 작동하는지가 굉장히 다양하지만 이를 일반적으로 두가지 종류로 나눠볼 수 있다. 하나는 imperative approach를 따르는 것이고 다른 하나는 declarative approach를 따르는 것이다. 이 위의 카테고리를 프로그래밍 언어 패러다임과 엮을 수 있다면 완벽하다.\nimperative approach는 순서를 제공하는 것이다. 이는 명령어나 지시사항의 순서를 정의하여 인프라가 최종적인 결과를 가지게 하는 것이다.\ndeclarative approach는 완하는 결과를 정의하는 것이다. 명백하게 원하는 결과로 가는 단계들의 순서를 정의하지 않고, 어떻게 최종 결과가 보여야 하는지만 정의한다.\nLearn Some Best Practices 이제 IaC 전략의 모범사례를 확인해 볼 것이다.\n 코드가 하나의 믿을 수 있는 소스로부터 나온다. 명시적으로 모든 인프라 설정을 설정 파일 안에 작성해야 한다. 설정 파일은 인프라 관리 문제에 대해서 단 하나의 관리포인트이다. 모든 설정 파일에 대해 Version Control을 하라. 이는 말할 필요도 없겠지만, 모든 코드는 source control이 되어야 한다. 인프라 스펙에 대한 약간의 문서화만 필요하다. 이 포인트는 첫번째의 논리적인 결과이다. 설정 파일이 단일 소스이므로, 문서화가 필요하지 않다. 외부 문서는 실제 설정과 싱크가 잘 안맞을 수 있지만 설정 파일은 그럴 이유가 없다. 설정을 테스트하고 모니터한다. IaC는 코드로 모든 코드와 같이 테스트될 수 있다. 따라서 가능하면 테스트해라. IaC에 대한 테스트와 모니터링을 하여 production에 어플리케이션을 배포하기 전에 서버에 문제가 있는지 확인할 수 있다. Resources At Your Disposal 다음은 IaC를 배우기 좋은 유용한 리소스들이다.\n Wikipedia’s definition Edureka’s Chef tutorial Ibexlabs’s.The Top 7 Infrastructure As Code Tools For Automation TechnologyAdvice’s Puppet vs. Chef: Comparing Configuration Management Tools Infrastructure as Code Saves You Time and Money IaC는 DevOps 움직임의 중요한 부분이다. cloud computing이 많은 수동 IT 고나리의 문제점을 해결하는 데 첫 번째 단계라고 본다면 IaC는 다음의 논리적인 단계가 될 것이다. 이는 cloud computing의 모든 가능성을 열어주고 개발자와 다른 전문가들로부터 수동적이고 에러가 발생하기 쉬운 업무를 없애준다. 또한 소프트웨어 개발 라이프사이클의 효율성을 늘리고 비용을 절감한다. IaC와 함께 Retrace같은 툴을 쓰는것도 좋다. Retrace는 코드 레벨의 Application Performance Manager 솔루션으로 전체 개발 라이프사이클에서 어플리케이션의 퍼포먼스를 관리하고 모니터하게 할 수 있다. 에러 추적이나 로그 관리, 어플리케이션 메트릭과 같은 또한 많은 다른 기능들을 가지고 있다.\n"},{"uri":"http://kimmj.github.io/cicd/deploy-strategy/","title":"Deploy Strategy","tags":["deploy","cicd","canary","blue-green","roll-out"],"description":"","content":"Deploy Strategy 실제 시스템을 운용할 때 중요하게 여겨지는 것 중 하나가 downtime을 없애는 것이다. 새로운 업데이트가 있을 때마다 해당 인스턴스가 동작하지 않는다면, 자주 업데이트 하는 것이 어려워질 수 있습니다. 따라서 deploy strategy를 가지고 어떻게 downtime을 줄이는지 알아보도록 하겠습니다.\nCanary canary deploy는 트래픽 비율을 바꾸어가며 배포하는 전략입니다. 이해하기 편하도록 Kubernetes 환경이라고 생각해 보도록 하겠습니다. (또는 LoadBalancer가 있어서 부하를 분산하고 있다고 생각하면 좋을 것 같습니다.) 이 때 업데이트된 버전을 따로 올리고 트래픽을 old:new = 100:0으로 줍니다. 이러면 새로운 버전이 정상적으로 실행할 준비가 될 때까지는 우리의 인스턴스가 정상적으로 동작하고 있을 것입니다.\n준비가 되었다면, old:new = 90:10처럼 약간의 트래픽을 새로운 버전으로 흘려줍니다. 이 어플리케이션이 만약 웹사이트라면, 전체 유저 중 10%의 사람만이 새로운 버전의 웹사이트를 보게 될 것입니다.\n이 때 각종 통계라던지 테스트를 통해 새로운 버전에 문제가 없는지 확인합니다. 혹시나 문제가 발생하더라도, 10%의 사람만이 문제를 경험하게 될 것입니다. 문제점이 발견되면 다시 old:new = 100:0으로 트래픽을 돌려버리면 이전의 잘 돌아가던 상태로 복구할 수 있습니다.\n이런식으로 새로운 버전의 트래픽을 능동적으로 조금씩 늘려가며 여러 지표를 확인하고 정상적이라고 판단되면 old:new = 0:100으로 변경합니다. 그 다음 이전 버전을 삭제하면 이제 완전히 새로운 버전으로 업데이트 된 것입니다.\n이 방식은 특정 부분을 운용중에 긴급 패치하는 경우 사용할 수 있는 전략입니다. 장애가 발생하더라도 큰 위험 부담이 없기 때문이죠.\n그러나 이를 위해서는 새로운 버전이 얼마나 이전 버전과 호환이 잘 되는지가 중요합니다. 만약 이전 버전과는 너무나도 다른 새로운 버전이 있다면 전체 어플리케이션은 정상적으로 동작하지 않을 수 있습니다.\n Blue-Green blue-green deploy는 두개의 버전을 동시에 올려놓고, 트래픽을 한번에 바꾸는 전략입니다.\n하나의 인스턴스에 대해 버전을 두가지 올립니다. 이 때, 트래픽의 비율은 old:new = 100:0으로 항상 이전 버전으로만 흐르도록 합니다. 그러다가 어느 순간 old:new = 0:100으로 트래픽을 아예 바꾸어버립니다. 그러면 유저는 항상 새로운 버전만 경험하게 될 것입니다.\n이렇게 새로운 버전을 운용하며 문제점을 파악합니다. canary와는 다르게 문제점이 있을 경우 해당 인스턴스가 아예 중단되어 downtime이 생깁니다. 이럴 때에는 다시 원래대로 트래픽을 old:new = 100:0으로 바꾸면 이전 버전으로 빠르게 roll back이 가능합니다.\n또한 canary에서는 긴급 패치하는 경우, 하나의 인스턴스에 대해서만 행해진다고 하였습니다. 그러나 blue-green의 경우 전체 패키지 또는 어플리케이션에 대해서도 사용할 수 있는 전략입니다. 물론 장비가 2배로 들겠지만, 트래픽만 바꾸면 되니 빠른 roll back이 가능하기 때문에 그 비용을 감수할 수 있습니다.\n당연하게도 하나의 인스턴스만 업데이트하는 경우에도 사용할 수 있습니다. 이 경우에는 다른 인스턴스들과 잘 호환이 되어야 합니다. 반면 전체 어플리케이션에 대해 할 경우 함께 설치되는 인스턴스들끼리의 호환성만 확인하면 됩니다. dependency가 복잡할 경우, 어플리케이션이 빠르게 변화하는 경우 사용해볼 수 있을 것입니다.\n roll out roll out은 하나의 인스턴스에 대해 Pod 또는 VM이 여러개 떠있다고 가정합니다.\n이 때, 하나씩 순차적으로 새로운 버전으로 변경합니다. downtime을 줄이고 싶다면 업데이트하는 동안에는 트래픽을 흐르지 않도록 하면 될 것입니다.\n이런식으로 순차적으로 새로운 버전으로 변경하여 최종적으로는 모든 노드에 대해 업데이트가 완료될 것입니다.\ncanary와 마찬가지로 다른 버전의 인스턴스들과 잘 동작해야함을 보장해야 합니다. 그래야 두 버전이 동시에 사용되고 있다고 하더라도 정상적으로 어플리케이션이 동작하게 될 것입니다.\n "},{"uri":"http://kimmj.github.io/kubernetes/concepts/pods/","title":"Pods","tags":["kubernetes","pod"],"description":"","content":"Pod Overview Pod의 이해 Pod는 Kubernetes에서 가장 작은 배포 오브젝트이며 쿠버네티스에서 관리하는 최소 관리 단위입니다. Pod는 cluster 안에서 실행중인 어떤 프로세스를 의미합니다. application container, 스토리지 리소스, 유일한 network ip, container가 어떻게 실행할지를 캡슐화한 것입니다.\n각각의 Pod는 주어진 application에서 단일 인스턴스를 수행합니다. 즉, 한가지 역할을 맡고 있다고 생각하시면 됩니다.. 따라서 application을 수직확장하고 싶다면 각 인스턴스에 대해 여러 Pod를 생성하면 된다. 그러면 동일한 역할을 하는 Pod가 늘어나니, 병렬적으로 처리가 가능할 것입니다.\nPod는 서비스 중에서 서로 연관성이 높은 프로세스를 지원하기 위해 디자인되었습니다. container는 리소스와 의존성들을 공유하고, 서로 통신하며, 언제/어떻게 종료하는지에 대해 서로 조정합니다. Pod 내의 container는 Networking과 Storage를 공유할 수 있습니다.\nPod 안에는 둘 이상의 container가 있을 수 있습니다. 이 때 Pod 내에 여러 container를 두는 것은 container가 정말 강하게 결합될 때 입니다. 예를 들어, 하나의 container는 web server로 shared volume에서 파일을 가져와 호스팅하는 역할을 하고, 나머지 하나의 side-car container는 외부에서 file을 pulling하여 shared volume에 올리는 역할을 하는 것입니다. 이처럼 둘간의 결합성이 큰 경우(여기서는 shared volume일 것입니다) 동일한 Pod에 위치할 수 있습니다.\ninit container는 실제 app container가 시작하기 전에 먼저 작업을 하는 container입니다.\nNetworking 각 Pod 단위로 네트워크 IP 주소를 할당받습니다. 그 내부의 container끼리는 localhost로 통신하게 되고 외부와의 통신을 위해선 공유중인 네트워크 리소스를 어떻게 분배할 지 합의 및 조정해야합니다. 예를 들어 Pod 단위로 생각해보면 하나의 IP를 가지게 되고 expose할 port들을 가지게 될 것입니다. IP는 상관 없지만 port의 경우 특정 container로 binding이 되어야 하기 때문에 각 container는 동일한 port를 expose할 수 없습니다. Pod 관점에서 보면 어느 container로 전달해주어야 하는지 모르기 때문이죠.\nStorage Pod는 공유하는 저장공간을 volumes로 지정합니다. 이렇게 하면 하나의 container만 재시동되는 경우에도 데이터를 보존할 수 있습니다.\nPod로 작업하기 Pod를 사용할 때에는 kubectl create pods처럼 controller 없이 Pod를 생성하는 것은 좋은 생각이 아닙니다. 이렇게 할 경우 Kubernetes의 장점들을 충분히 활용할 수 없습니다. 특히 self-healing을 하지 못하기 때문에 Pod가 떠있던 노드에 장애가 발생하면 Pod는 영영 복구되지 않을 수 있습니다. 반면 controller로 관리할 경우 self-healing을 지원하여 노드에 장애가 발생해도 다른 노드에 해당 Pod를 띄워서 계속하여 서비스를 할 수 있습니다.\nPod의 재시작과 container의 재시작을 혼동하면 안됩니다. Pod는 그 자체만으로 동작하지 않습니다. 오히려 Pod는 삭제되기 전까지 계속 유지가 되는, container가 동작하는 환경이라고 보면 됩니다. Pod를 VM을 사용하는 상황과 비유를 해보자면 Pod는 VM에서 하나의 VM에 실행하는 application들처럼 하나의 논리적 호스트에서 container들을 실행하는 개념입니다.\nPod 내의 container들은 IP주소와 port space를 공유합니다. 그리고 서로 localhost로 통신할 수 있습니다. 반면에 다른 Pod에 있는 container와는 Pod에 할당된 IP를 사용하여 통신할 수 있습니다.\nPod는 container처럼 임시적인 자원이기 때문에 삭제시 reschedule이 아닌 새로운 동일한 spec의 Pod를 새로운 UID(Unique ID)로 생성합니다. 즉, Pod가 삭제되면 그 안에 있는 내용들을 잃어버리게 됩니다. VM에 빗대자면 VM을 생성하는 template을 가지고 새로운 VM을 생성하며, 삭제할 경우 해당 VM을 완전히 삭제한다는 개념입니다. 이 때 volume도 Pod가 삭제되면 삭제됩니다.\nPod Lifecycle Pod의 status 필드는 PodStatus의 phase 필드입니다. Pod의 phase는 간단하게 Pod가 위치한 lifecycle의 상위 개념에서의 요약정보입니다.\nphase 의 value들에는 다음과 같은 것들이 있습니다.\n Pending : Pod가 kubernetes 시스템에 의해 받아들여졌지만 하나 이상의 container image가 생성되지 않은 상태. Running : Pod가 node에 바운드되고 모든 container들이 생성됨. 최소한 하나의 container가 실행 중이거나 시작 또는 재시작 중. Succeeded : 모든 container가 성공적으로 종료되었고, 재시작되지 않음. Failed : 모든 container가 정료되었고, 최소 하나의 container가 failure 상태. Unknown : 어떤 이유로 인해 Pod의 state를 얻어낼 수 없음. 보통 Pod의 호스트와 통신이 안되는 문제. Container probe kubelet이 container을 진단할 때 사용하는 것입니다. container에서 구현된 Handler를 호출하여 이러한 진단을 수행합니다.\n크게 3가지 probe가 있습니다.\n livenessProbe: container가 실행 중인지 나타냄. liveness probe가 실패하면 kubelet은 container를 죽이고, 이 container는 restart policy를 실행한다. redinessProbe: container가 service requests를 받을 준비가 되었는지 나타냄. readiness probe가 실패할 경우 endpoint controller는 Pod의 IP 주소를 Pod와 매칭되는 Service들의 endpoints에서 삭제한다. initial delay 이전의 default 값은 Failure이다. startupProbe: container 내부의 application이 시작되었는지를 나타냄. 다른 probe들은 startup probe가 성공할 때까지 비활성화 상태. livenessProbe를 사용해야하는 상황 livenessProbe는 container가 제대로 동작하지 않은 경우 제대로 동작할 수 있을 때까지 재시동하는 목적으로 사용합니다. 따라서 container가 제대로 동작하지 않을 때 실행하는 프로세스가 이미 있다면 굳이 사용하지 않아도 됩니다. 원래 목적대로 재시동을 하고 싶다면 livenessProbe를 지정하고 restartPolicy를 설정합니다.\nreadinessProbe를 사용해야하는 상황 Pod에 request를 보내면 트래픽은 그 내부의 container로 전달됩니다. 이 때, 해당 container가 제대로 동작을 하지 않는다면, 파드에 request를 보냈을 때 비정상적인 응답을 할 것입니다.\n따라서 Pod가 제대로 응답을 보내줄 수 있는 상황에만 해당 Pod로 트래픽을 전달하고 싶다면, readinessProbe를 사용합니다. Kubernetes는 readinessProbe가 실패하면 Service와 연결된 Endpoint에서 해당 Pod를 삭제합니다. 그러면 Service로 흐른 트래픽은 redinessProbe가 실패한 Pod로 흐르지 않게 됩니다.\n단순히 삭제시 트래픽이 안흐르도록 하고 싶다면 굳이 할 필요는 없습니다. 알아서 삭제시 Service와 연결된 Pod의 Endpoint를 삭제하기 때문입니다.\nstartupProbe를 사용해야하는 상황 startupProbe는 위의 두 probe들과는 약간 다른 성격을 가졌습니다. livenessProbe와 함께 사용이 되는데요, container가 initialDelaySeconds + failureThreshold × periodSeconds만큼의 시간이 지난 후에 정상동작을 할 경우(container가 작업을 시작하기까지 시간이 오래 걸리는 경우) livenessProbe에 의해 fail이 발생하고, 재시작 되는것을 막아 deadlock 상태를 방지해줍니다.\nRestart policy PodSpec에서 restartPolicy 필드에는 Always, OnFailure, Never를 사용할 수 있습니다. 그 중 default는 Always입니다.\nrestartPolicy는 Pod 내의 모든 container에 적용됩니다. 또한 restartPolicy는 exponential back-off delay로 재시작됩니다. 즉, 10초, 20초, 40초로 계속해서 일정수준까지 delay가 늘어납니다. 성공적으로 실행되고 나서 10분이 지나면 해당 delay는 초기화됩니다.\nPod lifetime 일반적으로 Pod는 사람 또는 컨트롤러가 명백하게 이를 지우지 않는 이상 유지됩니다. control plane은 Pod의 총 개수가 지정된 threshold를 초과하면(node마다 정해져 있습니다) 종료된 Pod들(Succeeded 또는 Failed)을 삭제합니다.\n컨트롤러는 3가지 타입이 있습니다.\n Job: batch computations처럼 종료될것으로 예상되는 Pod입니다. ReplicationController, ReplicaSet, Deployment: 종료되지 않을 것으로 예상되는 Pod입니다. DaemonSet: 머신마다 하나씩 동작해야하는 Pod입니다. Init Container Init container는 app image에서 사용할 수 없거나 사용하지 않는 setup script나 utility들을 포함할 수 있습니다. 실제 app container가 시작되기 전에 먼저 필요한 작업들을 수행하는데 사용됩니다.\nPod Specification에서 containers 배열과 같은 개위로 작성하면 Init container를 사용할 수 있습니다.\nInit container는 completion이 되기 위해 실행됩니다. 따라서 complete상태가 되면 재시작되지 않습니다. completion을 위해 실행되므로 당연하게도 readinessProbe는 사용할 수 없습니다.\ninit container는 여러개를 정의했을 경우 kubelet은 이를 순서대로 실행됩니다. 그리고 각 init container는 다음 init container가 실행되기 전에 반드시 성공적으로 종료되어야 합니다.\ninit container가 실패하면 성공할때까지 재시작합니다. 하지만 Pod의 restartPolicy가 Never이면 init container도 재시작하지 않습니다.\ninit container는 app container가 사용할 수 있는 대부분의 필드를 그대로 사용할 수 있습니다. 일반적인 container와의 차이점은 resource에 대해 다르게 관리된다는 것입니다. 자세한 내용은 공식 홈페이지의 docs를 확인하시기 바랍니다.\nInit container 사용하기 Init container는 app image에는 없는 utility나 custom code를 포함할 수 있습니다. Init container는 동일한 Pod 내에 있는 app container와는 다른 filesystem view를 가질 수 있습니다. 따라서 app container는 접근할 수 없는 Secret을 가지고 동작할 수 있습니다. Init container가 성공할 때까지 Pod의 app container들은 생성되지 않습니다. App container보다 안전하게 utility, custom code를 실행시킬 수 있습니다. 따라서 보안 취약점을 줄일 수 있습니다. 메인 app container를 실행할 때 필요한 configuration file에 필요한 value들을 주입할 때 init container를 사용할 수 있습니다.\nDetailed behavior Pod가 시작되는 동안 network와 volume들이 초기화 된 후 init container가 순서대로 실행되게 됩니다. 각 container는 반드시 다음 container가 실행되기 전까지 성공적으로 종료되어야 합니다.\nInit container에 대한 spec 변경은 container image에 대한것만 가능하다. 그리고 Init container는 idempotent1가 성립해야합니다.\nInit container가 실패 시 계속해서 재시작 되는 것을 막으려면 Pod에 activeDeadlineSeconds와 Container에 livenessProbe를 설정하면 막을 수 있습니다.\nResource 다루는 법 모든 init container에 대해 가장 높은 resource request나 limit은 effective init request/limit이라고 정의합니다. Pod의 effective request/limit은 다음보다 커야합니다. 모든 app container의 resource에 대한 request/limit의 합 resource에 대한 effective init request/lmit effective request/limits를 기준으로 스케쥴링합니다. 즉, init container의 resource는 Pod의 life 동안 사용되지 않음을 의미합니다. Pod의 effective QoS tier에서 QoS tier는 init container와 app container의 QoS tier와 같습니다. init container가 재시작되는 경우 user가 pod specification을 업데이트 하여 init container의 이미지가 변경되었을 경우입니다. App container image의 변화는 app container만 재시작시킵니다. Pod infrastructure container가 재시작 되었을 때 Init container가 실행됩니다. restartPolicy가 Always로 설정이 되어있는 상태에서 Pod가 재시작 되었을 때 init container가 이전 완료 상태를 저장한 것이 만료되거나 없을 경우 재시작될 수 있습니다. Disruptions Pod는 원래 누군가가(사람 또는 컨트롤러) 지우지 않는다면, 또는 피할 수 없는 하드웨어, 소프트웨어적인 에러가 아니라면 삭제되지 않습니다.\n여기서 unavoidable인 경우를 involuntary disruptions라고 부릅니다. involuntary disruption에는 다음과 같은 것들이 있습니다.\n hardware failure cluster administrator가 실수로 VM을 삭제 cloud provider나 hypervisor의 장애로 VM이 삭제됨 kernel panic cluster network partition에 의해 node가 cluster에서 사라짐 노드가 out-of-resource여서 pod의 eviction이 실행됨 voluntary disruption은 application이나 cluster administrator에 의해 시작된 동작들입니다. voluntary disruption에는 다음과 같은 것들이 있습니다.\n 해당 Pod를 관리하고 있던 delployment나 다른 controller의 삭제 deployment의 Pod template update가 재시작을 유발함 직접적으로 Pod를 삭제 Cluster Administrator는 다음이 disruption을 유발할 수 있습니다.\n Upgrade를 위한 Draining Node cluster를 scale down 하기 위해 Draining Node 특정 노드에 띄워야 하는 요구사항 때문에 기존에 있던 해당 노드에서 Pod를 제거 Dealing with Disruptions Pod에게 충분한 양의 resource 할당하기 고가용성을 원할경우 application을 복제하기 application을 rack또는 zone에 분배하기 How Disruption Budgets Work PodDisruptionBudget(PDB)를 각 application에 설정할 수 있습니다. 이는 voluntary disruption 상황에서 동시에 down될 수 있는 pod의 갯수를 제한합니다.\nPodDisruptionBudget(PDB)를 사용하려면 Cluster Manager는 Eviction API를 통해서 Pod를 삭제해야합니다. 즉, 직접 Pod나 Deployment를 삭제하게 되면 PDB를 사용하지 못하게 됩니다. Eviction API를 사용하는 예시에는 kubectl drain가 있습니다.\nPodDisruptionBudget(PDB)는 involuntary disruption 상황에서는 작동하지 않습니다. 하지만 몇개가 종료되는지는 기록하여 budget에 추가합니다.\nRolloing upgrade 때문에 Pod가 삭제되거나 사용 불가능한 상태일 때에도 PodDisruptionBudget(PDB)는 이를 카운트하지만 PDB때문에 제한되지는 않습니다. application의 업데이트 동안 발생한 장애처리는 controller spec에서 정의내린대로 실행합니다.\n 멱등법칙. 여러번 실행하더라도 동일한 결과를 냄.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n "},{"uri":"http://kimmj.github.io/css/greater-than-sign/","title":"Greater Than Sign","tags":["css"],"description":"","content":"\u0026gt;의 의미 \u0026gt;는 child-combinator 입니다.\n다음의 예시를 통해 정확히 어떤 역할을 하는지 알아보도록 하겠습니다.\n\u0026lt;div\u0026gt; \u0026lt;p class=\u0026#34;some_class\u0026#34;\u0026gt;Some text here\u0026lt;/p\u0026gt; \u0026lt;!-- Selected [1] --\u0026gt; \u0026lt;blockquote\u0026gt; \u0026lt;p class=\u0026#34;some_class\u0026#34;\u0026gt;More text here\u0026lt;/p\u0026gt; \u0026lt;!-- Not selected [2] --\u0026gt; \u0026lt;/blockquote\u0026gt; \u0026lt;/div\u0026gt; 위와 같은 예시에서 div \u0026gt; p.some_class는 div 바로 밑에 있는 p.some_class만을 선택합니다. \u0026lt;blockquote\u0026gt;로 감싸진 p.some_class는 선택되지 않습니다.\n이와는 다르게 space만 사용하는 descendant combinator는 두개의 p.some_class 모두를 선택합니다.\n"},{"uri":"http://kimmj.github.io/hugo/insert-comment/","title":"Hugo에 Comment 추가하기 (Utterance)","tags":["hugo","utterance"],"description":"","content":"댓글 서비스 선택 블로그를 운영하는데 관심을 가지기 시작하면서, 기본적으로 jekyll이나 hugo에는 댓글 기능이 없다는 것을 알게 되었습니다. static site를 만드는데 사실 댓글을 지원한다는게 이상한 상황이긴 하지요. 그래도 서드파티의 지원을 받으면 댓글 기능이 가능해집니다. 여러 블로그들을 탐방하며 git page 기능을 사용하는 블로그들에도 댓글이 있는것을 항상 봐왔으니까요.\n따라서 댓글을 어떻게 사용하는지 검색해보게 되었습니다. 대표적인 것이 Disqus 입니다. 실제로 많은 사이트들이 Disqus를 기반으로 댓글 기능을 사용합니다.\n저는 이 hugo 기반 블로그를 만드는 데 큰 도움을 준 https://ryan-han.com/post/etc/creating_static_blog/를 보고 Utterance에 대해 접하게 되었으며 개발자에게는 너무나도 친숙한 깃허브 기반이라는 점이 끌려서 이 서비스를 선택하게 되었습니다.\n적용 방법 적용하는 방법은 너무나도 쉽습니다.\n미리 댓글을 위한 레파지토리를 생각해둡니다(기존에 있는 레파지토리 혹은 댓글만을 위한 레파지토리). 저는 사이트를 렌더링해주는 kimmj.github.io 레파지토리로 선택하였습니다. https://utteranc.es/에 접속합니다. 중간쯤에 보이는 utterances app 하이퍼링크를 클릭합니다. 해당 앱을 적절한 레파지토리에 설치합니다. 설치한 레파지토리를 1번에서 접속한 사이트에 양식에 맞게 기입합니다. Blog Post ↔️ Issue Mapping 섹션에서 적절한 것을 선택합니다. 저는 default 옵션을 사용했습니다. Issue Label은 댓글로 생성된 이슈에 label을 달지, 단다면 어떤 label을 달지 선택하는 것입니다. 저는 comment로 작성했습니다. Theme을 선택합니다. 저는 default 옵션을 사용했습니다. 하단에 생성된 코드를 복사합니다. hugo에서 댓글이 보이게 될 위치에 8번에서 복사한 코드를 붙여넣습니다. 저의 경우 custom-comment.html이라는 파일에 붙여넣기 했습니다. 사이트에 제대로 뜨는지 확인합니다. 후기 이렇게 hugo에 댓글 기능을 추가하였습니다. 생각보다 너무나도 간단했네요. 이 댓글 기능을 하자고 생각하고 나서 적용까지 5분정도 걸렸던 것 같습니다. 댓글도 너무나 친숙한 레이아웃이라 정감이 가는 것 같네요.\nReference https://github.com/Integerous/TIL/blob/master/ETC/Hugo%2BGithub_Page.md https://utteranc.es/ "},{"uri":"http://kimmj.github.io/ubuntu/tools/tmux/","title":"Tmux","tags":["tmux","ubuntu"],"description":"","content":"tmux란? tmux는 하나의 화면에서 여러개의 터미널을 키고싶을 때 사용하는 프로그램으로, ubuntu를 설치하면 기본적으로 설치되는 프로그램입니다.\n다음과 같은 구조를 가집니다.\ntmux ├── session │ ├── windows │ │ ├── pane │ │ └── pane │ └── windows │ ├── pane │ └── pane └── session ├── windows │ ├── pane │ └── pane └── windows ├── pane └── pane session 사용법 먼저 가장 큰 단위인 session을 다루는 방법부터 시작해보도록 하겠습니다.\nsession 생성 tmux 위처럼 tmux를 생성할 수 있습니다. 이 경우 tmux session의 이름은 0부터 차례로 증가하는 숫자로 정의됩니다.\n여기에 session의 이름을 사용자가 정의할 수도 있습니다.\ntmux new -s \u0026lt;my-session\u0026gt; 이렇게 이름을 지어놓으면 용도에 따른 session을 구분할 때 좋습니다.\nsession에 접속하기 이번에는 이미 생성된 session에 접속하는 방법입니다.\ntmux a tmux a -t \u0026lt;my-session\u0026gt; tmux attach tmux attach -t \u0026lt;my-session\u0026gt; -t 옵션을 줘서 이름을 지정할 수도 있고, (임의로 생성된 숫자 또한 마찬가지입니다.) 옵션없이 실행할 경우 가장 최근 열린 session으로 접속합니다.\nsession 확인하기 session어떤 것들이 있는지 보려면 ls를 이용하면 됩니다.\ntmux ls 또는 이미 session에 들어간 상태에서, session들의 리스트를 봄과 동시에 미리보기 화면으로 어떤 작업중이었는지도 볼 수 있습니다.\n[prefix] s 여기서 [prefix]는 일반적으로 Ctrl+b를 의미하며, 사용자에 의해 변경될 수 있습니다.\nsession에서 빠져나오기 session을 빠져나오는 방법에는 두가지가 있습니다.\n 완전히 session을 로그아웃하여 session 삭제하기 session이 계속 돌아가는 상태에서 빠져나오기 1번의 경우는 모든 pane에서 log out을 하면 되므로 생략하도록 하겠습니다. 두번째 session이 계속 돌아가는 상태에서 빠져나오는 방법은 다음과 같습니다.\n[prefix] d session 죽이기 tmux 바깥에서 session을 없애려면 들어가서 로그아웃을 통해 끄는 방법도 있을테지만, kill-session이라는 명령어를 통해서도 session을 없앨 수 있습니다.\ntmux kill-session -t \u0026lt;my-session\u0026gt; session 이름 바꾸기 session에 들어가 있는 상태에서 현재 session의 이름을 변경할 수 있습니다.\n[prefix] $ 그러면 상태표시줄에서 session의 이름을 정해줄 수 있습니다.\nwindows 사용법 windows는 session내의 tab과 같은 존재입니다. session은 큰 사용 목적으로 묶어준다면 windows는 그에따라 tab으로 관리할 필요가 있을 때 사용하면 좋습니다. 물론 session과 pane만 가지고 사용해도 되지만, 그보다 더 유연하게 하려한다면 windows도 알아두는 것이 좋습니다.\nwindows의 생성 windows를 관리하고자 한다면, 우선 session에 들어가 있는 상태여야 합니다.\n[prefix] c 위처럼 windows를 생성하고 나면 아래 tmux 상태표시줄에 0:bash- 1:bash*와 같은 형태로 windows가 추가됨을 볼 수 있습니다. 여기서 *는 현재 사용중인 windows를 의미합니다.\nwindows 움직이기 먼저 기본적으로 앞, 뒤로 움직이는 방법입니다.\n[prefix] n # next window [prefix] p # previous window session에서 미리보기를 사용하여 순회했던 것처럼, windows도 list들을 미리보기형식으로 순회할 수 있습니다.\n[prefix] w windows 이름 변경하기 특정 window에 들어가있는 상태에서 해당 window의 이름을 변경할 수 있습니다.\n[prefix] , windows 죽이기 현재 window를 죽이려면 로그아웃을 하는 방법도 있지만, 강제로 죽이는 방법도 존재합니다.\n[prefix] \u0026amp; panes 다루기 pane이란 tmux의 화면을 분할하는 단위입니다. tmux를 사용하는 가장 큰 이유라고 볼 수 있습니다.\npane 분할하기 [prefix] % # vertical split [prefix] \u0026#34; # horizontal split 위와같은 방법으로 pane을 생성할 수 있습니다.\npane 이동하기 먼저 기본적으로 방향키를 이용하여 움직일 수 있습니다.\n[prefix] \u0026lt;방향키\u0026gt; 일정시간이 지나면 pane내에서의 방향키 입력으로 전환되어 pane을 움직일 수 없으므로 빠르게 움직여줍니다.\npane 위치 바꾸기 pane들을 rotate하는 방법입니다.\n[prefix] ctrl+o # 시계방향으로 회전 [prefix] alt+o # 반시계방향으로 회전 또는 하나를 이동할 수도 있습니다.\n[prefix] { # move the current pane left [prefix] } # move the current pane right pane 크게 보기 pane을 여러개 쓰다가 하나만 크게 보고싶은 경우가 있을 수 있습니다.\n[prefix] z 돌아가는 방법 또한 같은 명령어를 통해 할 수 있습니다.\n[prefix] z pane을 새로운 window로 분할하기 특정 텍스트를 마우스로 복사하거나 여러가지 상황에서 새로운 window로 분할하는 것이 편한 경우가 있습니다.\n[prefix] ! 모든 pane에서 동시에 입력하기 tmux를 통해서 여러개의 서버에 ssh 접속을 한 뒤, 동시에 같은 입력을 하게 할 수도 있습니다.\n[prefix] :set synchronize-panes yes (on) [prefix] :set synchronize-panes no (off) Tips tmux에는 기본 내장된 layout이 있습니다. 이를 통해서 pane들을 resize하지 않고 기본 형식에 맞게 쉽게 변경이 가능합니다. 그 중 가장 자주 사용할 수 있는 것은 다음 두가지입니다.\n[prefix] alt+1 # 수직 [prefix] alt+2 # 수평 Reference https://gist.github.com/MohamedAlaa/2961058 https://gist.github.com/andreyvit/2921703 "},{"uri":"http://kimmj.github.io/prometheus/federation/","title":"Federation","tags":["prometheus","federation"],"description":"","content":"What is Federation 영어 의미 그대로는 \u0026ldquo;연합\u0026quot;이라는 뜻입니다. 즉, Prometheus의 Federation은 여러개의 Prometheus에서 Metric을 가져와 계층구조를 만드는 것을 의미합니다.\n위의 그림에서 너무나도 잘 표현이 되어 있습니다. 그림에서 보시면 상위에 있는 Prometheus에서 하위의 Dev, Staging, Production쪽으로 화살표가 간 것을 볼 수 있습니다. 이는 아래에 있는 Prometheus가 http(s)://\u0026lt;url\u0026gt;/federation으로 보여주는 Metric들을 위쪽에 있는 Prometheus에서 scrape하기 때문입니다.\n저의 상황을 설명해드리고 지나가도록 하겠습니다. 저는 Kubernetes Cluster가 Dev(Canary), Staging, Production과 비슷하게 3개 있었습니다. 여기서 Spinnaker를 통해 Dev에 새로운 이미지들을 배포할 것이고, 이에 대한 Metric을 Canary Analysis를 통해 분석하여 Dev로 배포된 이미지가 이전 Staging의 이미지와 어떻게 다른지 등을 점수화하여 Staging 서버에 배포를 할지 말지 결정하도록 해야하는 상황이었습니다.\n이 때, Spinnaker의 설정상 Prometheus를 연동하고 나서 Canary Config를 설정할 때 하나의 Prometheus만 바라보도록 할 수 있었습니다. 따라서 여러대의 Promethus의 Metric을 비교하기 위해서는 여러대의 Prometheus가 가지고 있는 Metric을 상위개념의 Prometheus가 scrape하도록 해야했습니다. 어떻게 해야하는지 검색해본 결과 Federation이라는 기능이 있는 것을 알게 되었습니다.\nHow to configure Prometheus Federation Prerequisites 우선 여러대의 Prometheus가 필요합니다. 그리고 이를 하나로 모아줄 또 다른 Prometheus가 필요합니다.\n저의 경우 docker-compose를 통해 Prometheus를 구동하여 다른 서버의 Prometheus Metric을 가져오도록 설정하였습니다. Install에 관해서는 다른 문서를 참고해 주시기 바랍니다.\nConfiguring federation 다음은 공식 사이트에 나온 federation의 구성입니다.\nscrape_configs: - job_name: \u0026#39;federate\u0026#39; scrape_interval: 15s honor_labels: true metrics_path: \u0026#39;/federate\u0026#39; params: \u0026#39;match[]\u0026#39;: - \u0026#39;{job=\u0026#34;prometheus\u0026#34;}\u0026#39; - \u0026#39;{__name__=~\u0026#34;job:.*\u0026#34;}\u0026#39; static_configs: - targets: - \u0026#39;source-prometheus-1:9090\u0026#39; - \u0026#39;source-prometheus-2:9090\u0026#39; - \u0026#39;source-prometheus-3:9090\u0026#39; job_name job_name은 static_configs[0].targets에 적힌 Prometheus Metric에 어떤 job=\u0026quot;\u0026lt;job_name\u0026gt;\u0026quot;을 줄지 결정하는 것입니다. 이를 통해 저는 Canary, Stage 서버를 구분하였습니다.\nscrape_interval 얼마나 자주 Metric을 긁어올 지 결정하는 것입니다.\nmetrics_path 어떤 path에서 Metric을 가져오는지 설정합니다. 보통의 경우 federation으로 설정하면 됩니다.\nparams 실제로 \u0026lt;PrometheusUrl\u0026gt;/federation으로 접속해보면 아무것도 뜨지 않습니다. /federation은 param로 매칭이 되는 결과만 리턴하며, 없을 경우 아무것도 리턴하지 않습니다. 따라서 이는 필수 필드이고, 원하는 job만 가져오게 하거나 {job=~\u0026quot;.+\u0026quot;}와 같은 방법으로 모든 Metric을 가져오게도 할 수 있습니다.\nstatic_configs 어떤 Prometheus에서 Metric을 가져올지 결정하는 것입니다.\nValidation 위와같이 설정을 한 뒤, Prometheus에 접속하여 Targets에 들어가 봅니다. 리스트에 설정한 job_name들이 떠있고, UP인 상태로 있으면 정상적으로 구성이 된 것입니다.\nReference https://prometheus.io/docs/prometheus/latest/federation/\n"},{"uri":"http://kimmj.github.io/spinnaker/canaryanalysis/canary-analysis/","title":"Canary Analysis","tags":["canary","canary-update","spinnaker"],"description":"","content":"Spinnaker Canary Analysis Spinnaker에는 Canary Analysis라는 자동 분석 도구가 있습니다. Kayenta라는 micro service를 사용하는데, 이를 통해 자동으로 canary deploy가 괜찮은 버전인지를 확인해 줍니다.\n그러나 이 툴은 Spinnaker에서 사용하기에 여간 어려운 것이 아닙니다. 제일 먼저 봉착하는 난관은 바로 \u0026ldquo;어떻게 Canary Analysis를 활성화 하는가?\u0026ldquo;입니다.\n이곳에 방법이 나와있지만, 사실 저도 엄청 많이 헤멨습니다. 저는 bare-metal 환경에서 Kubernetes cluster를 구축하였었고, aws나 azure, gcp는 사용하지 못하는 상황었습니다. (물론 지금도 집에서 VM으로 로컬에 구성하였지만, cloud platform은 언제나 과금때문에 꺼려지게 됩니다.)\n이런 상황에서 어떻게 Canary Analysis를 활성화했는지부터, 어떻게 이를 통해 Metric을 비교하는지까지 한번 알아보도록 하겠습니다.\nPrerequisites 첫번째로 metric service를 선택해야 합니다. 여러가지가 있을 수 있겠지만, 저는 로컬에서 사용할 수 있는 Prometheus를 사용할 것입니다.\n두번째로 가져온 metric의 결과들을 저장해 놓을 storage service가 필요합니다. 저는 Spinnaker를 구성할 때 minio를 storage service로 구축을 했었으므로, 여기에서도 마찬가지로 minio를 통해 데이터를 저장할 것입니다.\nHow to enable Canary Analysis 제일 먼저 hal command를 통해서 Canary Analysis를 활성화시켜야 합니다.\nhal config canary enable 그다음엔 Prometheus를 canary analysis에 사용되도록 설정할 것입니다.\nhal config canary prometheus enable hal config canary prometheus account add my-prometheus --base-url http://192.168.8.22/9090 이처럼 Prometheus 콘솔창이 보이는 url을 입력하면 됩니다. 저는 docker-compose를 통해서 9090 port로 expose 시켰으므로 위와같이 적어주었습니다.\n그 다음에는 metric provider를 설정합니다. 앞서 말했듯이 여기서는 Prometheus를 사용할 것입니다.\nhal config canary edit --default-metrics-store prometheus hal config canary edit --default-metrics-account my-prometheus 위의 두 설정으로 Prometheus가 default metric store로 선택되었습니다. 이는 물론 나중에 canary configuration에서 원하는 것으로 선택할 수도 있습니다. 또한 default account도 my-prometheus라는 이름으로 선택해 주었습니다.\n이번엔 default storage account를 설정할 것입니다. 공식 docs에서는 minio에 관련된 설정방법이 잘 나와있지 않습니다.\n하지만 hal command를 잘 보시면 어떻게 해야할지 감이 약간은 잡히실 것입니다.\n--api-endpoint에는 minio의 url을 적고, --aws-access-key-id에는 minio의 ID였던 minio를 입력합니다. 그리고 --aws-secret-access-key는 minio의 PW인 minio123을 입력합니다.\nhal config artifact s3 account add my-minio \\ --api-endpoint http://192.168.8.22:9000 \\ --aws-access-key-id minio \\ --aws-secret-access-key minio123 그 뒤에는 위에서 입력했던 my-minio storage account를 Canary Analysis에 사용하도록 설정하면 됩니다.\nhal config canary edit --default-storage-account my-minio Validate 제대로 설정을 마쳤으면 pipeline의 config에서 Feature 탭에 Canary가 추가된 것을 볼 수 있습니다. 이를 활성화하면 본격적으로 Canary Analysis를 사용할 수 있습니다.\nReference https://www.spinnaker.io/guides/user/canary/\nhttps://www.spinnaker.io/guides/user/canary/config/\n"},{"uri":"http://kimmj.github.io/ubuntu/change-hostname/","title":"Hostname 변경하기","tags":["hostname","ubuntu"],"description":"","content":"hostname을 바꾸는 일은 흔치 않지만 최초 셋업할 때 많이 사용하곤 합니다.\n# hostnamectl set-hostname \u0026lt;host name\u0026gt; hostnamectl set-hostname wonderland 변경 후 터미널을 끄고 재접속을 하면 변경된 사항을 볼 수 있습니다.\nhostname "},{"uri":"http://kimmj.github.io/hugo/ibiza/font-change/","title":"Font Change","tags":["font","hugo"],"description":"","content":"Ibiza 프로젝트를 진행하는데 폰트가 마음에 들지 않았습니다. 따라서 저는 폰트를 변경하기로 마음먹었습니다.\n먼저, 폰트 설정을 어디서 하는지 알아낼 필요가 있었습니다.\nfind . | grep font 결과를 보니, theme 폴더 안에 제가 사용하는 hugo-theme-learn 테마에서 static/fonts/ 폴더에 폰트들을 저장해두고 있었습니다. 그렇다면 어느 파일에서 어떤 폰트를 사용한다고 설정할까요?\nhugo-theme-learn폴더로 이동하여 어디에 사용되는지 확인해보았습니다.\ngrep -ri \u0026#34;font\u0026#34; 결과가 길게 나오는데요, 여기서 static/css/theme.css 안에 폰트에 대한 설정을 한 것이 보였습니다. 그 파일을 보니, @font-face라는 설정이 보이네요. 여기서 Work Sans라는 폰트를 불러오고 있었습니다.\n이 폰트를 Noto Sans CJK KR이라는 폰트로 바꾸려고 합니다. 따라서 먼저 폰트를 다운로드 받아야 합니다.\n다운로드 페이지 : https://www.google.com/get/noto/#sans-kore\n여기에서 다운로드 버튼을 눌러 폰트를 다운받습니다.\ncurl -o noto-mono.zip https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKkr-hinted.zip 이를 my-custom-theme 폴더 내의 static/fonts 폴더 안에다가 압축해제할 것입니다.\nmv noto-mono.zip mj-custom-theme/static/fonts unzip noto-mono.zip rm noto-mono.zip README 그러고나서 font-face 설정을 바꾸어 보도록 하겠습니다. 처음에는 이 설정으로 폰트가 정말 바뀌는지 확인해보기 위해 현재 사용중인 폰트의 url 부분을 Noto Mono 폰트로 변경해보았습니다.\n@font-face { font-family: \u0026#39;Work Sans\u0026#39;; font-style: normal; font-weight: 500; src: url(\u0026#34;../fonts/NotoSansMonoCJKkr-Bold.otf?#iefix\u0026#34;) format(\u0026#34;embedded-opentype\u0026#34;), url(\u0026#34;../fonts/NotoSansMonoCJKkr-Bold.otf\u0026#34;) format(\u0026#34;woff\u0026#34;), url(\u0026#34;../fonts/Work_Sans_500.woff2\u0026#34;) format(\u0026#34;woff2\u0026#34;), url(\u0026#34;../fonts/Work_Sans_500.svg#WorkSans\u0026#34;) format(\u0026#34;svg\u0026#34;), url(\u0026#34;../fonts/Work_Sans_500.ttf\u0026#34;) format(\u0026#34;truetype\u0026#34;); } 하지만 예상과 다르게 변경되지 않았습니다. 확인해보니 이는 font-weight이라는 설정때문이었습니다. body에 대한 font-weight은 300으로 설정이 되어있었고, 따라서 제가 설정한 폰트가 아닌 font-weight: 300인 폰트를 선택하게 된 것입니다.\n다시한번 body쪽의 font-weight을 500으로 바꾸어 실험해보았습니다.\nbody { font-family: \u0026#34;Work Sans\u0026#34;, \u0026#34;Helvetica\u0026#34;, \u0026#34;Tahoma\u0026#34;, \u0026#34;Geneva\u0026#34;, \u0026#34;Arial\u0026#34;, sans-serif; font-weight: 500; line-height: 1.6; font-size: 18px !important; } 그러자 제가 원하는 폰트로 변경이 된 것을 확인하였습니다. 다시 위로 돌아가서 @font-face 설정을 저의 폰트 이름으로 변경하고 제가 원래 하려던 폰트로 변경하였습니다. font-weight: 300으로 다시 돌려놓았고, 새로운 폰트를 font-weight: 300으로 주었습니다.\n@font-face { font-family: \u0026#39;Noto Mono Sans CJK KR \u0026#39;; font-style: normal; font-weight: 300; src: url(\u0026#34;../fonts/NotoSansMonoCJKkr-Regular.eot?#iefix\u0026#34;) format(\u0026#34;embedded-opentype\u0026#34;), url(\u0026#34;../fonts/NotoSansMonoCJKkr-Regular.woff\u0026#34;) format(\u0026#34;woff\u0026#34;), url(\u0026#34;../fonts/NotoSansMonoCJKkr-Regular.woff2\u0026#34;) format(\u0026#34;woff2\u0026#34;), url(\u0026#34;../fonts/NotoSansMonoCJKkr-Regular.svg#NotoSansMonoCJKkr\u0026#34;) format(\u0026#34;svg\u0026#34;), url(\u0026#34;../fonts/NotoSansMonoCJKkr-Regular.ttf\u0026#34;) format(\u0026#34;truetype\u0026#34;); } 여기서 보시면 format 속성들이 많은 것을 볼 수 있습니다. 이는 브라우저별로 지원하는, 지원하지 않는 폰트들에 대해서 처리를 해주기 위한 것입니다. 저는 폰트를 다운로드 받았을 때 otf 포멧밖에 없었습니다. 따라서 다른 포멧들로 변경해 줄 필요가 있었습니다.\nfont converter : https://onlinefontconverter.com/\n위 사이트에서 제가 필요한 eot, woff, woff2, svg, ttf 파일들로 변환후 저장했습니다.\n이제 body의 css에서 font-family 맨 앞에 앞서 정의한 폰트를 지정해줍니다.\nbody { font-family: \u0026#34;Noto Sans Mono CJK KR\u0026#34;, \u0026#34;Work Sans\u0026#34;, \u0026#34;Helvetica\u0026#34;, \u0026#34;Tahoma\u0026#34;, \u0026#34;Geneva\u0026#34;, \u0026#34;Arial\u0026#34;, sans-serif; font-weight: 300; line-height: 1.6; font-size: 18px !important; } 확인해보니 제대로 적용이 되었네요.\nbefore font change after font change Reference https://wit.nts-corp.com/2017/02/13/4258 https://aboooks.tistory.com/153 https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization?hl=ko "},{"uri":"http://kimmj.github.io/ubuntu/network/netplan/","title":"Netplan으로 static IP 할당받기","tags":["ubuntu-18.04","netplan","static-ip"],"description":"","content":"유선 static IP 할당 다음과 같이 static-IP-netplan.yaml을 작성합니다.\nnetwork: version: 2 ethernets: enp3s0: dhcp4: no dhcp6: no addresses: [ 192.168.1.26/24 ] gateway4: 192.168.1.1 nameservers: addresses: [ 8.8.8.8, 8.8.4.4 ] 하나씩 살펴보도록 하겠습니다.\n ethernetes: 유선랜 설정입니다. enp3s0 설정을 사용할 랜카드입니다. dhcp4, dhcp6: dynamic으로 IP를 할당받는 dhcp를 disable한 것입니다. addresses: 사용할 IP 및 CIDR입니다. gateway4: IP가 사용하는 gateway입니다. nameservers: dns 주소입니다. 8.8.8.8과 8.8.4.4를 사용합니다. WIFI static IP 할당 wifis: wlp2s0: dhcp4: no dhcp6: no addresses: [ 192.168.8.26/24 ] gateway4: 192.168.8.30 nameservers: addresses: [ 8.8.8.8, 8.8.4.4 ] access-points: \u0026#34;my-SSID\u0026#34;: password: \u0026#34;my-password\u0026#34; 하나씩 살펴보도록 하겠습니다.\n wifis: wifi에 대한 설정입니다. wlp2s0: 무선 랜카드입니다. access-points: 사용할 wifi에 대한 설정입니다. \u0026quot;my-SSID\u0026quot;: wifi의 SSID 즉, wifi 이름입니다. password: 해당 wifi의 비밀번호를 적으면 됩니다. 만약 ubuntu server를 사용한다면, wifi 관련 패키지는 초기에 설치되지 않습니다. 따라서 wpasupplicant를 설치해주어야 합니다.\nsudo apt install wpasupplicant "},{"uri":"http://kimmj.github.io/hugo/hugo-with-html/","title":"HUGO로 HTML이 되지 않을 때 가능하게 하는 방법","tags":["hugo","html"],"description":"","content":"Hugo는 markdown을 기본적으로 사용하지만 html을 이용해서 좀 더 다양하게 커스터마이징이 가능한 장점도 가지고 있습니다.\n하지만 저는 처음에 html 코드를 사용하게 되면 \u0026lt;!-- raw HTML omitted --\u0026gt;와 같은 줄로 대치가 되곤 했습니다. 구글링 결과 이는 Hugo의 버전이 0.60.0으로 되면서부터 기본적으로 disable 시켰기 때문입니다.\n따라서 다음과 같이 조치를 하면 간단하게 해결이 가능합니다.\n[markup.goldmark.renderer] unsafe= true 위와 같은 설정을 config.toml에 추가하기만 하면 됩니다. 추가를 한 뒤 다시 확인해보면 정상적으로 html 코드가 적용된 모습을 볼 수 있습니다.\n"},{"uri":"http://kimmj.github.io/spinnaker/tips/pipeline-expressions/","title":"Pipeline Expressions","tags":["spinnaker","pipeline"],"description":"","content":"Spinnaker는 배포를 자동화할 때 사용합니다. 그렇기 때문에 자동화를 위해선 다른 곳에서 사용된 값들을 가지고 와야할 필요성이 생기기도 합니다.\n이 문서에서는 그럴 때 사용할 수 있는 pipeline function에 대해 알아보도록 하겠습니다.\npipeline functions pipeline에서 다른 pipeline의 값들 불러오기 Note: Pipeline expression syntax is based on Spring Expression Language (SpEL).\n 위의 Note에도 적었듯이, Spinnaker는 SpEL을 기반으로 Expressions를 사용합니다. SpEL에 대해 이미 잘 알고있다면 너무나도 좋겠지만, 저는 익숙하지가 않았기 때문에 많은 시행착오를 거쳐서 습득을 하게 되었습니다.\n기본적으로 ${ expression }의 형태를 가지게 됩니다.\n여기서 한가지 기억해 두어야 할 것은 nested가 되지 않는다는 것입니다. 즉, ${ expression1 ${expression2} }가 되지 않습니다.\n언제 pipeline expression을 사용하나요 pipeline expression은 Spinnaker UI로는 해결할 수 없는 문제들을 해결하여줍니다. 예를 들어 특정 stage가 성공했는지의 여부에 따라 stage를 실행할지, 말지 결정하는 방법을 제공해 줍니다. 또는 가장 최근에 deploy된 pod를 알아낸다거나, spinnaker를 통한 canary analysis를 할 때 비교할 두가지 대상을 선택하기 위해 사용할 수도 있습니다.\nSpinnaker는 모든 파이프라인을 JSON 형태로도 관리할 수 있기 때문에, UI에는 없는 값들도 입력할 수 있습니다. 이렇게 좀 더 유연한 방법으로 Spinnaker를 이용하고자 한다면 pipeline expression은 꼭 알아두어야 합니다.\n원하는 값을 어떻게 찾나요 pipeline이 구동되고 나면, Details를 누르고 Source를 눌렀을 때 해당 pipeline의 실행결과가 json형태로 출력됩니다. 이를 VS Code나 다른 편집기를 이용하여, json으로 인식하게 한 뒤, 자동 들여쓰기를 하면 보기 좋게 만들어줍니다.\n이를 통해서 어떤 값을 내가 사용할 지 확인하여 pipeline expression을 작성하면 됩니다.\n내가 작성한 pipeline expression은 어떻게 테스트하나요 작성한 pipeline expression을 테스트하기 위해 파이프라인을 구동한다는 것은 끔직한 일입니다. Spinnaker는 이를 테스트하기 위해 API endpoint를 제공합니다. 즉, 파이프라인을 다시 구동시키지 않고도 어떤 결과값이 나오는지 확인할 수 있다는 것을 의미합니다.\n테스트 방법은 간단합니다. 다음과 같이 curl을 통해 endpoint로 테스트하면 됩니다.\nPIPELINE_ID=[your_pipeline_id] curl http://api.my.spinnaker/pipelines/$PIPELINE_ID/evaluateExpression \\ -H \u0026#34;Content-Type: text/plain\u0026#34; \\ --data \u0026#39;${ #stage(\u0026#34;Deploy\u0026#34;).status.toString() }\u0026#39; 여기서 api.my.spinnaker는 Gate의 Service를 보고 포트를 참조하여 작성하면 됩니다. 기본값은 localhost:8084입니다. 이렇게 하면 Deploy라는 stage가 성공했을 때 다음과 같은 결과를 볼 수 있습니다.\n{\u0026#34;result\u0026#34;: \u0026#34;SUCCEEDED\u0026#34;} Spinnaker가 expression을 통해 결과를 만들어내지 못한다면 다음과 같이 에러와 로그가 발생합니다.\n{ \u0026#34;detail\u0026#34;: { \u0026#34;{ #stage(\\\u0026#34;Deploy\\\u0026#34;).status.toString() \u0026#34;: [ { \u0026#34;description\u0026#34;: \u0026#34;Failed to evaluate [expression] Expression [{ #stage( #root.execution, \\\u0026#34;Deploy\\\u0026#34;).status.toString() ] @0: No ending suffix \u0026#39;}\u0026#39; for expression starting at character 0: { #stage( #root.execution, \\\u0026#34;Deploy\\\u0026#34;).status.toString() \u0026#34;, \u0026#34;exceptionType\u0026#34;:\u0026#34;org.springframework.expression.ParseException\u0026#34;, \u0026#34;level\u0026#34;:\u0026#34;ERROR\u0026#34;, \u0026#34;timestamp\u0026#34;:1531254890849 } ] }, \u0026#34;result\u0026#34;:\u0026#34;${#stage(\\\u0026#34;Deploy\\\u0026#34;).status.toString() \u0026#34; } Reference Spinnaker Docs: https://www.spinnaker.io/guides/user/pipeline/expressions/\n"},{"uri":"http://kimmj.github.io/spinnaker/tips/","title":"Tips","tags":[],"description":"","content":"Spinnaker Tips spinnaker를 운영하며 생기는 팁들을 모아보았습니다.\n Pipeline Expressions "},{"uri":"http://kimmj.github.io/ansible/create-vm-with-ansible-libvirt/","title":"Create Vm With Ansible Libvirt","tags":["ansible","libvirt"],"description":"","content":"Ansible은 어떠한 프로세스를 자동화 할 때 사용할 수 있는 툴입니다. 그리고 libvirt는 linux 환경에서 qemu를 이용하여 VM을 생성할 때 사용하는 python 모듈입니다.\n이 두가지를 합하여 Ansible을 통해 VM을 생성하는 방법에 대해 알아보도록 하겠습니다.\nansible-role-libvirt-vm 참조 Github : https://github.com/stackhpc/ansible-role-libvirt-vm\n위의 Github 프로젝트는 libvirt를 ansible에서 사용할 수 있도록 만든 오픈소스입니다. 이를 이용하여 ansible-playbook을 통해 VM을 생성해 볼 것입니다.\n이를 로컬에 clone 합니다.\ngit clone https://github.com/stackhpc/ansible-role-libvirt-vm 테스트 환경 저는 Ubuntu 18.04.3 Desktop을 사용하고 있습니다. 그리고 설치에 사용될 iso는 제 포스트에서 작성한 적이 있었던 preseed.cfg를 이용한 자동 설치 이미지입니다. 따라서 이미지를 넣고 부팅만 하면 실행할 수 있습니다.\nplay.yaml 저는 이러한 play.yaml 파일을 사용하였습니다.\n여기서 cdrom을 사용하였는데, 이미지는 baked-ubuntu.iso를 사용하였습니다.\n또한 장비들에 대한 설정을 xml로 추가적으로 하고싶어서 xml_file을 설정해 주었습니다.\nxml_file또한 업로드 해두었습니다.\n 네트워크는 설정을 빼놓을 경우 설치중에 확인창이 발생하여 기본적으로 NAT를 사용하도록 하였습니다. 이는 필요에 따라 변경을 해야 합니다. 또한 enable_vnc의 경우 virt-manager를 통해 상황의 경과를 확인하고 싶어서 추가하였습니다.\n위의 파일들을 workspace에 두시면 됩니다.\nTest 이렇게까지 한 뒤 play.yaml이 있는 위치에서 시작합니다.\n그러면 ansible-playbook은 ansible-role-libvirt-vm이라는 role을 해당 위치에서 검색하고, 실행이 될 것입니다.\nansible-playbook play.yaml 실행 중 sudo 권한이 필요하다고 할 수도 있습니다. 이럴 경우 sudo su로 잠시 로그인 후 exit로 빠져나오시면 에러가 발생하지 않습니다.\n확인 virt-manager를 통해 GUI 환경에서 실제로 잘 되고 있는지 확인할 수 있습니다.\nvirt-manager preseed.cfg를 사용한 이미지라면 30초 후 설치 언어가 자동으로 영어로 설정이 되면서 계속해서 설치가 진행될 것입니다.\n마치며 vm을 생성하는 일이 잦다면, 이 또한 굉장히 귀찮은 일이 아닐 수 없습니다. 소규모가 아닌 대규모로의 확장성을 생각한다면 당연히 자동화를 하는 것이 올바른 접근이라고 생각합니다.\nVM 설치 자동화의 방법이 여러가지가 있을 것이고 이 방법 또한 그 여러가지 방법 중 하나입니다.\n더 좋은, 더 편한 방법이 있다면 알려주시면 감사하겠습니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/unattended-ubuntu/","title":"추가 입력절차(prompt) 없이 Ubuntu 설치하는 이미지 만들기","tags":["ubuntu-18.04-server","preseed.cfg"],"description":"","content":"어디에 좋을까 Ubuntu Server를 설치하기 위해서는 많은 추가 입력이 있어야 합니다. 사용자가 어떻게 설치하기를 원하는지 모르기 때문에, 또 다양한 옵션을 사용자가 선택하기 위해서는 어찌보면 당연한 것이겠지요. 하지만 만약 똑같은 설정을 사용할 것인데, 여러대의 서버에 OS를 설치하는 상황이라고 생각해보면 정말 암울합니다. 온전히 시간을 OS 설치에만 투자하자니 이건 간단한 업무로 인해 다른 업무를 보지 못하게 됩니다. 또 다른 업무와 동시에 하자니 다음 입력창이 뜰 때인지 한번씩 확인해 주어야 합니다.\n따라서 어차피 같은 설정을 한다면, 이러한 설정을 미리 해 놓는 방법이 Ubuntu iso 파일 내부에 있을 것이라고 추측했습니다. 분명 누군가가 이런 불편함을 해결했으리라 생각했죠. 다행이 몇번의 구글링을 통해 preseed.cfg라는 파일이 제가 말했던 사용자의 입력을 미리 정해놓는 파일이라는 것을 알 수 있었습니다.\n이 preseed.cfg 파일을 잘만 활용한다면, 서버에 OS를 설치할 때 불필요한 시간 낭비를 줄일 수 있을 것입니다.\n차라리 VM이었다면, 그냥 VM을 복사해서 IP나 MAC, hostname 같은 것들만 변경해도 됐을 수 있습니다. 하지만 preseed.cfg를 이해하게 되면 언제 어디서든 내가 원하는 설정을 해주는 우분투 설치 파일을 만들 수 있을 것입니다.\n사전 준비 먼저, 설정을 넣어줄 Ubuntu 18.04 Server가 필요합니다. 물론 Ubuntu 18.04 Desktop에도 적용이 될 것으로 보입니다. (검색했을 때 대부분이 Desktop 설치 이미지에 관한 내용이었으니까요.)\n여기서 중요한 점은 live라고 적혀있는 이미지가 아니어야 합니다. live가 붙은 것은 인터넷으로 파일들을 다운로드 받게 되고, 그럴 경우 오프라인 설치가 필요한 환경에서는 적합하지도 않고 작업할 때 필요한 파일 또한 없습니다.\n두번째로 중요한 점은 amd64입니다. 처음에 잘못받고 arm64를 다운받았었는데, 내부 파일들의 폴더 명도 다르고 동작방식도 달라 구글링을 통해 amd64 이미지를 따로 받았습니다.\npreseed.cfg 작성 제가 설정했던 preseed.cfg 파일은 다음과 같습니다.\n 설명은 후에 추가하도록 하겠습니다.\niso 파일 생성하기 크게 순서를 정한다면 이렇게 됩니다.\n initrd.gz를 압축해제한 뒤 preseed.cfg 관련 정보를 initrd.gz에 추가 다시 initrd.gz로 압축 md5sum을 통한 checksum 재생성 genisoimage를 통한 부팅용 이미지 생성 그러나 preseed.cfg를 수정할 때마다 이를 반복하는 것은 여간 귀찮은 일이 아닐 수 없습니다. 그래서 저는 이를 bakeIsoImage.sh이라는 간단한 shell 프로그램으로 만들어서 iso파일을 생성하도록 하였습니다.\n 설치 테스트 위의 방식대로 진행을 했다면 baked-ubuntu.iso라는 파일이 생성되었을 것입니다. 이를 virt-manager나 virtual box등을 통해 가상머신을 생성하여 설치 테스트를 합니다.\n설치를 하면서 아무런 입력을 하지 않았다면, 원래의 의도대로 잘 설치가 된 것이라고 볼 수 있겠네요.\n마치며 preseed.cfg라는 엄청나게 유용한 방법이 있음에도 불구하고, 공식적인 가이드 자체가 많이 없는 상황입니다. 어떤 옵션들이 있는지도 잘 모르고, 설명도 자세히 되어있지 않았습니다. 단지 주어진 것이라고는 공식 문서에서 예시로 제공하는 preseed.cfg 파일 하나와, 다른사람들이 작성해 놓은 파일들 뿐이었습니다.\n저 또한 입력없이 설치하는 우분투 설치 이미지를 만들기 위해 고군분투했습니다. 누군가가 이 글을 통해서 환경에 맞는 설정을 해주는 우분투 설치 이미지를 생성하여 자동화를 할 수 있게된다면 정말 좋을 것 같습니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/how-to-edit-boot-parameter-during-install/","title":"Ubuntu 설치 시 Boot Parameter를 수정하기","tags":["ubuntu","install","boot parameter"],"description":"","content":"Ubuntu 설치할 때 boot parameter가 필요한 상황이 간혹 발생할 수 있습니다.\n특히 저의 경우, preseed.cfg를 수정하기 위해 인스톨러가 질의하는 것이 preseed.cfg의 어떤것과 대응이 되는지를 보기 위해 DEBCONF_DEBUG=5라는 옵션을 boot parameter로 주어야 했습니다. 이 때 사용할 수 있는 방법을 소개드립니다.\n먼저 평소와 같이 ubuntu를 설치하기 위해 설치 이미지를 삽입합니다. 그 다음에는 언어를 선택하시면, 다음으로 넘어가기 전에 메뉴가 뜹니다.\n이 상태에서 F6을 누르시면 옵션을 선택할 수 있고, 이 때 ESC키를 누르면 boot parameter가 하단에 보일 것입니다. 여기서 원하는 boot parameter를 입력하면 됩니다.\n이 때, 위아래 방향키를 누르게 되면 입력했던 내용이 사라지게 됩니다. 따라서 미리 맨 위 install ubuntu에 커서를 올리고 수정하시기 바랍니다.\ninstall 시에 설정으로 넣어버리기 preseed.cfg로 미리 질문에 대한 답을 다 정할 수 있었듯이, boot parameter 또한 미리 설정할 수 있습니다. 해당 파일은 iso 파일을 압축해제 하였을 때, /isolinux/txt.cfg 파일 내에 있습니다.\ngrep -ri \u0026#39;initrd\u0026#39; . 이렇게 검색해 보았을 때 quiet ---이라고 적힌 것들이 있는데, --- 뒤에가 boot parameter로 쓰이는 것들입니다.\nvim으로 /isolinux/txt.cfg 파일을 열고 원하는 설정을 기입하면 됩니다.\n이렇게 원하는 boot parameter를 적었다면, 다시 md5sum을 통해 체크섬을 만들어주어야 합니다. 이에 대한 내용은 앞선 [포스트]({% post_url 2020-01-05-unattended-ubuntu %})에서도 확인할 수 있으니 bakeIsoImage.sh 스크립트를 참조하여 md5sum을 하고 iso 파일을 만들면 됩니다.\n"},{"uri":"http://kimmj.github.io/ubuntu/how-to-use-sudo-without-password/","title":"sudo를 password 없이 사용하기","tags":["sudo","passwordless","ubuntu"],"description":"","content":"/etc/sudoers는 sudo를 사용할 수 있는 파일입니다. 이 파일을 열어보면 다음과 같은 글이 적혀 있습니다.\n Please consider adding local content in /etc/sudoers.d/ instead of directly modifying this file\n 즉, 직접 이 파일을 수정해서 sudo 권한을 주지 말고, /etc/sudoers.d/ 폴더 내에 파일을 추가하라는 의미입니다.\n이 곳에는 /etc/sudoers와 마찬가지로 계정에 대한 설정을 추가할 수 있습니다. 그리고 /etc/sudoers에서는 \u0026ldquo;NOPASSWD\u0026quot;라는 옵션을 주어 password없이 타 계정의 권한을 가지게 만들 수 있습니다.\n이 두가지를 종합하여 내 linux 계정이 sudo 명령어를 입력할 때, 즉 root 권한을 가지게 될 때 password를 입력하지 않도록 설정할 수 있습니다.\nexport ACCOUNT=$(whoami) echo \u0026#34;$ACCOUNTALL = (root) NOPASSWD:ALL\u0026#34; | sudo tee /etc/sudoers.d/$ACCOUNT sudo chmod 0440 /etc/sudoers.d/$ACCOUNT 이제 sudo 명령어를 쳐도 더 이상 password를 입력하라는 출력이 뜨지 않습니다.\n"},{"uri":"http://kimmj.github.io/hugo/hugo-build-git-action/","title":"Git Action으로 hugo build 자동화 하기","tags":[],"description":"","content":"Github Action은 Github에서 제공하는 CI/CD 솔루션이다. 유저는 uses 라는 명령어로 원하는 작업을 불러올 수 있다.\n현재 내가 설정했던 Jenkins를 통한 빌드 및 배포는 이사하며 네트워크 환경이 변경되고, github에서 token을 통한 push만 허용되는 변경 등으로 인해 더이상 실행되고 있지 않았다. 또한 항상 포스트를 작성하려면 VM을 켜야했기에, 너무나 귀찮은 일이 아닐 수 없었다.\n따라서 Github Action 통해서 기존에 로컬에서 Jenkins를 통해 hugo 파일을 빌드해서 다른 repo에 올리는 것을 Jenkins를 사용하지 않고 Github Action을 통해 빌드하고 배포하도록 수정했다. 이를 통해 VM에 대한 dependency를 없앨 수 있었으며, 어디서나 내 repo에 push만 하면 포스팅이 자동으로 업데이트 되도록 변경하였다.\nworkflows 전체 플로우를 확인한 뒤 하나씩 자세히 확인해보도록 하자.\n master 브랜치로 새로운 내용 삽입 git checkout 을 통해 submodule, git contents를 pull Github secrets 에 config.toml 파일을 저장 후 불러오기 hugo --minify 로 ./public 폴더에 내용 생성 google adsense를 위한 파일 삽입 github page용 repo에 파일 push 간단하게 모든 플로우가 하나의 container에서 이루어진다고 생각하면 좀 더 이해하기 쉬울 것이다. 해당 container에다가 sudo apt install -y 로 필요한 파일들을 다운받을 수도 있고, 원하는 명령어를 마음껏 사용할 수도 있다. 그리고 물론, 실행 후에는 삭제가 된다.\n이를 기반으로 한 Github Action 파일은 아래와 같다.\nname: github pages on: push: branches: - master # Set a branch to deploy jobs: deploy: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: token: ${{ secrets.GIT_TOKEN }} submodules: recursive - name: copy config.toml run: echo \u0026#34;${CONFIG_TOML}\u0026#34; \u0026gt; config.toml env: CONFIG_TOML: ${{ secrets.config }} - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; # extended: true - name: Build run: hugo --minify - name: site-verification run: echo \u0026#34;${{ secrets.SITE_VERIFICATION }}\u0026#34; \u0026gt; ./public/google34bf590cfe76298c.html - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} external_repository: kimmj/kimmj.github.io publish_branch: master # default: gh-pages publish_dir: ./public master 브랜치로 새로운 내용 삽입 기존처럼 새로운 포스팅을 생성하는 부분이다. hugo new --kind contents Hugo/hubo-build-git-action.md 처럼 생성하면 된다.\ngit checkout 을 통해 submodule, git contents를 pull - uses: actions/checkout@v2 with: token: ${{ secrets.GIT_TOKEN }} submodules: recursive 여기서 ${{ secrets.GIT_TOKEN }} 은 PAT (Personal Access Token) 이다. 이 토큰을 이용해서 hugo 폴더에 있는 themes 아래 submodule들을 불러온다.\nGithub secrets 에 config.toml 파일을 저장 후 불러오기 - name: copy config.toml run: echo \u0026#34;${CONFIG_TOML}\u0026#34; \u0026gt; config.toml env: CONFIG_TOML: ${{ secrets.config }} 내 config.toml 에는 credential같은 정보가 들어가있어서, github에 파일을 올릴 수 없었다. 이 파일은 이전에 git-secrets 라는걸로 해결을 했었는데, github에 이제 secret을 올릴 수 있는 기능이 생겨서, 여기다 그냥 올려버렸다. 이를 echo를 통해 repo에 함께 담아 주었다.\nhugo --minify 로 ./public 폴더에 내용 생성 - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; # extended: true - name: Build run: hugo --minify hugo --minify 를 입력하게 되면 실제로 ./public 폴더에 정적 HTML 파일들이 저장된다. 이 파일들은 github page를 위해 사용될 것이다.\ngoogle adsense를 위한 파일 삽입 - name: site-verification run: echo \u0026#34;${{ secrets.SITE_VERIFICATION }}\u0026#34; \u0026gt; ./public/google34bf590cfe76298c.html 예전에 adsense를 달 때 site-verification 파일을 넣었었다. 이게 이번에도 필요한지는 정확하게 기억이 안나지만, 혹시몰라서 그냥 넣어주었다.\ngithub page용 repo에 파일 push - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} external_repository: kimmj/kimmj.github.io publish_branch: master # default: gh-pages publish_dir: ./public 여기서 ACTIONS_DEPLOY_KEY 는 create-ssh-deploy-key 를 보고 따라하여 만들었다.\n이렇게 하면 kimmj/kimmj.github.io repo에 master branch로 ./public 에 있는 파일들이 push된다. 이 때 기존에 있던 파일을 모두 지운 뒤 push 하는 방식이므로 따로 필요한 static file이 해당 repo에 반드시 있어야 할 경우, 위에서 내가 했던 방법대로 secret에 담고 echo로 넣어둔다던지, workaround를 찾아서 해결하면 된다.\n"},{"uri":"http://kimmj.github.io/tags/iptables/","title":"iptables","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/kubernetes/","title":"kubernetes","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/service/","title":"service","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/","title":"Tags","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/components/","title":"components","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/cpu-manager/","title":"cpu-manager","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/k8s/","title":"k8s","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/resources/","title":"resources","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/split/","title":"split","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/tar/","title":"tar","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/tgz/","title":"tgz","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/ubuntu/","title":"ubuntu","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/samba/","title":"samba","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/bridge/","title":"bridge","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/container/","title":"container","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/docker/","title":"docker","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/docker-compose/","title":"docker-compose","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/network/","title":"network","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/docker-registry/","title":"docker-registry","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/harbor/","title":"harbor","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/certified-kubernetes-administrator/","title":"Certified-Kubernetes-Administrator","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/cka/","title":"CKA","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/jenkins/","title":"jenkins","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/workspace/","title":"workspace","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/targetport/","title":"targetport","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/insecure-registry/","title":"insecure-registry","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/login-message/","title":"login-message","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/motd/","title":"motd","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/bgimg-darken/","title":"bgimg-darken","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/css/","title":"css","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/google-analytics/","title":"google-analytics","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/hugo/","title":"hugo","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/git/","title":"git","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/git-secret/","title":"git-secret","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/github/","title":"github","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/reboot/","title":"reboot","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/tmux/","title":"tmux","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/sudo/","title":"sudo","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/oh-my-zsh/","title":"oh-my-zsh","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/zsh/","title":"zsh","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/base64/","title":"base64","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/editor/","title":"editor","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/file/","title":"file","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/log/","title":"log","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/stern/","title":"stern","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/gitignore/","title":"gitignore","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/linux/","title":"linux","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/port/","title":"port","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/pipe/","title":"pipe","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/watch/","title":"watch","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/alias/","title":"alias","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/python/","title":"python","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/passwordless/","title":"passwordless","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/ssh/","title":"ssh","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/tunneling/","title":"tunneling","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/install/","title":"install","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/iac/","title":"IaC","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/infrastructure-as-code/","title":"infrastructure-as-code","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/blue-green/","title":"blue-green","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/canary/","title":"canary","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/cicd/","title":"cicd","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/deploy/","title":"deploy","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/roll-out/","title":"roll-out","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/pod/","title":"pod","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/concepts/","title":"concepts","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/prometheus/","title":"prometheus","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/utterance/","title":"utterance","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/federation/","title":"federation","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/canary-update/","title":"canary-update","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/spinnaker/","title":"spinnaker","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/air-gaped/","title":"air-gaped","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/minio/","title":"minio","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/hostname/","title":"hostname","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/font/","title":"font","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/halyard/","title":"halyard","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/proxy/","title":"proxy","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/netplan/","title":"netplan","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/static-ip/","title":"static-ip","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/ubuntu-18.04/","title":"ubuntu-18.04","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/html/","title":"html","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/pipeline/","title":"pipeline","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/ansible/","title":"ansible","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/libvirt/","title":"libvirt","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/preseed.cfg/","title":"preseed.cfg","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/ubuntu-18.04-server/","title":"ubuntu-18.04-server","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/tags/boot-parameter/","title":"boot parameter","tags":[],"description":"","content":""},{"uri":"http://kimmj.github.io/categories/","title":"Categories","tags":[],"description":"","content":""}]