fix: long descriptions appearing in meta tag (#42)

* fix: delete all posts

* chore: Pull-Request [blog-7-12-2023] from Obsidian (#41)

* PUSH NOTE : 수학 공부에 대한 고찰.md

* PUSH NOTE : Rules of Inference with Coq.md

* PUSH NOTE : 블로그 이주 이야기.md

* PUSH ATTACHMENT : blog-logo.png

* PUSH ATTACHMENT : github-publisher.png

* PUSH NOTE : 10. StatefulSets - Deploying Replicated Stateful Applications.md

* PUSH ATTACHMENT : k8s-10.jpeg

* PUSH NOTE : 09. Deployments - Updating Applications Declaratively.md

* PUSH ATTACHMENT : k8s-09.jpeg

* PUSH NOTE : 08. Accessing Pod Metadata and Other Resources from Applications.md

* PUSH ATTACHMENT : k8s-08.jpeg

* PUSH NOTE : 07. ConfigMaps and Secrets - Configuring Applications.md

* PUSH ATTACHMENT : k8s-07.jpeg

* PUSH NOTE : 06. Volumes - Attaching Disk Storage to Containers.md

* PUSH ATTACHMENT : k8s-06.jpeg

* PUSH NOTE : 05. Services - Enabling Clients to Discover and Talk to Pods.md

* PUSH ATTACHMENT : k8s-05.jpeg

* PUSH NOTE : 04. Replication and Other Controllers - Deploying Managed Pods.md

* PUSH ATTACHMENT : k8s-04.jpeg

* PUSH NOTE : 03. Pods - Running Containers in Kubernetes.md

* PUSH ATTACHMENT : k8s-03.jpeg

* PUSH NOTE : 02. First Steps with Docker and Kubernetes.md

* PUSH ATTACHMENT : k8s-02.jpeg

* PUSH NOTE : 01. Introducing Kubernetes.md

* PUSH ATTACHMENT : k8s-01.jpeg

* DELETE FILE : _posts/Development/Kubernetes/2021-02-28-01-introducing-kubernetes.md

* DELETE FILE : _posts/Development/Kubernetes/2021-03-07-02-first-steps-with-docker-and-kubernetes.md

* DELETE FILE : _posts/Development/Kubernetes/2021-03-21-04-replication-and-other-controllers.md

* DELETE FILE : _posts/Development/Kubernetes/2021-04-18-08-accessing-pod-metadata-and-other-resources-from-applications.md

* DELETE FILE : _posts/Mathematics/Coq/2023-07-08-rules-of-inference-with-coq.md
This commit is contained in:
2023-07-13 10:36:00 +09:00
committed by GitHub
parent c335e8ff7d
commit 6be7f42f68
14 changed files with 4718 additions and 4705 deletions

View File

@@ -1,203 +1,203 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops, docker] tags: [kubernetes, sre, devops, docker]
title: "01. Introducing Kubernetes" title: "01. Introducing Kubernetes"
date: "2021-02-28" date: "2021-02-28"
github_title: "2021-02-28-01-introducing-kubernetes" github_title: "2021-02-28-01-introducing-k8s"
image: image:
path: /assets/img/posts/k8s-01.jpeg path: /assets/img/posts/k8s-01.jpeg
--- ---
![k8s-01.jpeg](../../../assets/img/posts/k8s-01.jpeg) _Overview of Kubernetes Architecture (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-1)_ ![k8s-01.jpeg](../../../assets/img/posts/k8s-01.jpeg) _Overview of Kubernetes Architecture (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-1)_
기존에는 소프트웨어가 커다란 덩어리였지만 최근에는 독립적으로 작동하는 작은 **마이크로서비스**(microservice)로 나뉘고 있다. 이들은 독립적으로 동작하기 때문에, 개발하고 배포하거나 스케일링을 따로 해줄 수 있다는 장점이 있으며, 이 장점은 빠르게 변화하는 소프트웨어의 요구사항을 반영하기에 적합하다. 기존에는 소프트웨어가 커다란 덩어리였지만 최근에는 독립적으로 작동하는 작은 **마이크로서비스**(microservice)로 나뉘고 있다. 이들은 독립적으로 동작하기 때문에, 개발하고 배포하거나 스케일링을 따로 해줄 수 있다는 장점이 있으며, 이 장점은 빠르게 변화하는 소프트웨어의 요구사항을 반영하기에 적합하다.
마이크로서비스를 여러 개 띄우기 시작하면 이들을 효율적으로 관리하는 방법이 필요하다. 마이크로서비스를 여러 개 띄우기 시작하면 이들을 효율적으로 관리하는 방법이 필요하다.
**쿠버네티스**(Kubernetes)는 하드웨어 인프라를 추상화하여 데이터 센터 (혹은 소프트웨어가 운영되는 서버나 클라우드)를 커다란 하나의 리소스로 바라볼 수 있게 해준다. **쿠버네티스**(Kubernetes)는 하드웨어 인프라를 추상화하여 데이터 센터 (혹은 소프트웨어가 운영되는 서버나 클라우드)를 커다란 하나의 리소스로 바라볼 수 있게 해준다.
## 1.1 쿠버네티스가 필요한 이유 ## 1.1 쿠버네티스가 필요한 이유
--- ---
### 1.1.1 하나의 거대한 앱에서 마이크로서비스로 ### 1.1.1 하나의 거대한 앱에서 마이크로서비스로
소프트웨어가 거대한 하나의 앱이면 오랜 기간 유지 보수를 거치면서 지나치게 복잡해지고, 의존성이 늘어나기 마련이다. 소프트웨어가 거대한 하나의 앱이면 오랜 기간 유지 보수를 거치면서 지나치게 복잡해지고, 의존성이 늘어나기 마련이다.
서버에 부하가 늘어나게 되면 **스케일링**을 해야한다. 서버에 부하가 늘어나게 되면 **스케일링**을 해야한다.
#### 스케일링의 종류 #### 스케일링의 종류
- **Vertical scaling** (scaling up): CPU/메모리 추가 등, 코드를 수정할 필요는 없지만 비싸다. - **Vertical scaling** (scaling up): CPU/메모리 추가 등, 코드를 수정할 필요는 없지만 비싸다.
- **Horizontal scaling** (scaling out): 서버 증설 등, vertical scaling 에 비하면 싼 편이지만, 코드를 수정해야 하기도 하며, 때로는 불가능한 경우도 있다 (ex. DB) - **Horizontal scaling** (scaling out): 서버 증설 등, vertical scaling 에 비하면 싼 편이지만, 코드를 수정해야 하기도 하며, 때로는 불가능한 경우도 있다 (ex. DB)
만약 앱이 하나의 거대한 덩어리라면, 일부가 스케일링이 불가능하면 전체도 스케일링 할 수 없다. 한편 이를 작은 마이크로서비스로 나누면? 만약 앱이 하나의 거대한 덩어리라면, 일부가 스케일링이 불가능하면 전체도 스케일링 할 수 없다. 한편 이를 작은 마이크로서비스로 나누면?
마이크로서비스는 독립적인 프로세스로 동작하며, 다른 마이크로서비스와 API를 이용하여 통신한다. 독립적이기 때문에 해당 마이크로서비스가 하려는 일에 적합한 언어를 사용하여 서비스를 만들면 된다. 또 각 마이크로서비스 개별적으로 개발하고 배포할 수 있다. 다른 마이크로서비스에 변경 사항이 생기더라도 API만 유지된다면 의존성을 크게 고려하지 않아도 된다. 마이크로서비스는 독립적인 프로세스로 동작하며, 다른 마이크로서비스와 API를 이용하여 통신한다. 독립적이기 때문에 해당 마이크로서비스가 하려는 일에 적합한 언어를 사용하여 서비스를 만들면 된다. 또 각 마이크로서비스 개별적으로 개발하고 배포할 수 있다. 다른 마이크로서비스에 변경 사항이 생기더라도 API만 유지된다면 의존성을 크게 고려하지 않아도 된다.
따라서 마이크로서비스는 서비스별로 스케일링해주면 되며, 부하가 심한 마이크로서비스만 스케일링 해주면 되고, 나머지 부분은 그대로 두면 된다! 따라서 마이크로서비스는 서비스별로 스케일링해주면 되며, 부하가 심한 마이크로서비스만 스케일링 해주면 되고, 나머지 부분은 그대로 두면 된다!
#### 마이크로서비스의 단점 #### 마이크로서비스의 단점
마이크로서비스로 애플리케이션을 나누게 되면 각 서비스끼리 통신을 하게 될텐데 스케일링을 많이 하면 할수록 통신을 더 많이 해야한다. 복잡도가 증가하게 된다. 복잡한 시스템을 효율적으로, 에러 없이 운영하는 것은 어렵다. 마이크로서비스로 애플리케이션을 나누게 되면 각 서비스끼리 통신을 하게 될텐데 스케일링을 많이 하면 할수록 통신을 더 많이 해야한다. 복잡도가 증가하게 된다. 복잡한 시스템을 효율적으로, 에러 없이 운영하는 것은 어렵다.
디버깅할 때 골치아프다. 문제가 여러 마이크로서비스에 걸쳐있을 수도 있다. 디버깅할 때 골치아프다. 문제가 여러 마이크로서비스에 걸쳐있을 수도 있다.
마이크로서비스 마다 요구하는 라이브러리가 다를 수 있고, 같은 라이브러리를 사용하지만 버전이 다를 수도 있다. 의존성을 관리하기 어려워 질 수도 있다. 마이크로서비스 마다 요구하는 라이브러리가 다를 수 있고, 같은 라이브러리를 사용하지만 버전이 다를 수도 있다. 의존성을 관리하기 어려워 질 수도 있다.
### 1.1.2 앱에 일관된 환경 제공 ### 1.1.2 앱에 일관된 환경 제공
개발 환경과 운영/배포 환경은 다르다! 개발 환경과 운영/배포 환경은 다르다!
개발 환경과 배포 환경을 동일하게 만들어서 같은 운영체제, 라이브러리, 시스템 설정, 네트워크 환경 등을 똑같이 할 수 있다면 배포 환경에서만 생기는 문제들 때문에 골치 아파야 할 일은 줄어들 것이다.[^1] 개발 환경과 배포 환경을 동일하게 만들어서 같은 운영체제, 라이브러리, 시스템 설정, 네트워크 환경 등을 똑같이 할 수 있다면 배포 환경에서만 생기는 문제들 때문에 골치 아파야 할 일은 줄어들 것이다.[^1]
### 1.1.3 지속적인 배포: DevOps and NoOps ### 1.1.3 지속적인 배포: DevOps and NoOps
**DevOps**: 애플리케이션 개발, 배포와 운영을 담당 **DevOps**: 애플리케이션 개발, 배포와 운영을 담당
개발자가 애플리케이션 배포와 운영에 참여하게 되면서 사용자의 필요나 이슈를 잘 파악할 수 있게 되고, 운영에 있어서 발생하는 어려움을 더 잘 이해하게 된다. 개발자가 애플리케이션 배포와 운영에 참여하게 되면서 사용자의 필요나 이슈를 잘 파악할 수 있게 되고, 운영에 있어서 발생하는 어려움을 더 잘 이해하게 된다.
**NoOps**: 개발자가 하드웨어 인프라에 대한 지식 없이, 또 운영 팀의 도움 없이 서비스를 배포 **NoOps**: 개발자가 하드웨어 인프라에 대한 지식 없이, 또 운영 팀의 도움 없이 서비스를 배포
## 1.2 컨테이너 기술 소개 ## 1.2 컨테이너 기술 소개
--- ---
쿠버네티스에서는 **리눅스의 컨테이너 기술**을 사용하여 애플리케이션의 동작을 분리한다. 쿠버네티스에서는 **리눅스의 컨테이너 기술**을 사용하여 애플리케이션의 동작을 분리한다.
### 1.2.1 컨테이너란? ### 1.2.1 컨테이너란?
마이크로서비스를 운영하려면 각 서비스별로 독립적인 환경을 제공해야 한다. 이를 하기 위해서는 각 마이크로서비스를 **가상 머신**(virtual machine)에 올리면 된다. 마이크로서비스를 운영하려면 각 서비스별로 독립적인 환경을 제공해야 한다. 이를 하기 위해서는 각 마이크로서비스를 **가상 머신**(virtual machine)에 올리면 된다.
하지만 VM은 너무 시스템 자원을 많이 소모하고, 각 VM마다 설정을 따로 해야하니 피곤하다. 하지만 VM은 너무 시스템 자원을 많이 소모하고, 각 VM마다 설정을 따로 해야하니 피곤하다.
대안으로 **컨테이너 기술**을 사용한다. 여러 개의 서비스가 하나의 머신에서 작동하면서도 각 서비스에게 독립적인 환경을 제공할 수 있게 되며, VM에 비해 훨씬 가볍다. 대안으로 **컨테이너 기술**을 사용한다. 여러 개의 서비스가 하나의 머신에서 작동하면서도 각 서비스에게 독립적인 환경을 제공할 수 있게 되며, VM에 비해 훨씬 가볍다.
컨테이너에서 돌아가는 프로세스는 호스트(컨테이너를 실행한) 머신의 운영체제 아래에서 돌아간다. (다른 프로세스들과 마찬가지!) 컨테이너에서 돌아가는 프로세스는 호스트(컨테이너를 실행한) 머신의 운영체제 아래에서 돌아간다. (다른 프로세스들과 마찬가지!)
#### VM과의 차이점 #### VM과의 차이점
VM은 독자적인 운영체제를 사용하기 때문에 시스템 프로세스가 필요하고 이는 추가적인 리소스의 소모로 이어진다. (윈도우 VM에 RAM 4GB씩 준다고 생각하면 몇 개를 띄울 수 있을지...) VM은 독자적인 운영체제를 사용하기 때문에 시스템 프로세스가 필요하고 이는 추가적인 리소스의 소모로 이어진다. (윈도우 VM에 RAM 4GB씩 준다고 생각하면 몇 개를 띄울 수 있을지...)
반면 컨테이너는 호스트 머신에서 돌아가는 프로세스이기 때문에 추가적인 시스템 프로세스가 필요 없어서 애플리케이션이 필요한 리소스만 소모하게 된다. VM에 비해 훨씬 가볍기 때문에 한 머신에서 여러 컨테이너를 돌릴 수 있게 된다. 반면 컨테이너는 호스트 머신에서 돌아가는 프로세스이기 때문에 추가적인 시스템 프로세스가 필요 없어서 애플리케이션이 필요한 리소스만 소모하게 된다. VM에 비해 훨씬 가볍기 때문에 한 머신에서 여러 컨테이너를 돌릴 수 있게 된다.
VM을 사용하게 되면 hypervisor 가 하드웨어 자원을 가상 자원(virtual resource)으로 나누어 각 VM안의 OS가 사용할 수 있게 해준다. VM 안에서 돌아가는 애플리케이션은 VM의 OS에 system call 을 하게 되고, VM의 커널은 hypervisor를 통해 호스트의 CPU에서 명령을 수행하게 된다. VM을 사용하게 되면 hypervisor 가 하드웨어 자원을 가상 자원(virtual resource)으로 나누어 각 VM안의 OS가 사용할 수 있게 해준다. VM 안에서 돌아가는 애플리케이션은 VM의 OS에 system call 을 하게 되고, VM의 커널은 hypervisor를 통해 호스트의 CPU에서 명령을 수행하게 된다.
컨테이너는 호스트의 OS 커널에서 돌아가기 때문에 가상화 작업이 불필요하다. 결국 시스템 자원을 VM에 비해 적게 소모하므로 실행도 빠르다. 컨테이너는 호스트의 OS 커널에서 돌아가기 때문에 가상화 작업이 불필요하다. 결국 시스템 자원을 VM에 비해 적게 소모하므로 실행도 빠르다.
#### 컨테이너의 동작 원리 #### 컨테이너의 동작 원리
- **Linux Namespace** - **Linux Namespace**
각 프로세스가 독립적으로 시스템을 바라보게 되어 독립적인 환경을 제공한다. 같은 namespace 에 있는 자원만 확인할 수 있으므로, 여러 namespace 를 만들고 자원을 할당하면 프로세스 입장에서는 시스템 내에서 독립적으로 돌아가는 것 처럼 보일 것이다. 각 프로세스가 독립적으로 시스템을 바라보게 되어 독립적인 환경을 제공한다. 같은 namespace 에 있는 자원만 확인할 수 있으므로, 여러 namespace 를 만들고 자원을 할당하면 프로세스 입장에서는 시스템 내에서 독립적으로 돌아가는 것 처럼 보일 것이다.
- **Linux Control Groups** (cgroups) - **Linux Control Groups** (cgroups)
각 프로세스가 사용할 수 있는 자원에 제한을 걸어둔다. 각 프로세스가 사용할 수 있는 자원에 제한을 걸어둔다.
### 1.2.2 Docker (도커) ### 1.2.2 Docker (도커)
애플리케이션을 패키징하고, 배포하고 실행하는 플랫폼이다. 애플리케이션을 패키징하고, 배포하고 실행하는 플랫폼이다.
VM 이미지가 아니라 용량이 더 작은 **컨테이너 이미지**를 사용한다. VM 이미지가 아니라 용량이 더 작은 **컨테이너 이미지**를 사용한다.
컨테이너 이미지는 여러 layer 로 구성되어 있어서 이를 공유하거나 재사용 할 수 있다. 같은 layer 가 필요한 다른 서비스에서 그대로 가져다 쓸 수 있게 된다. 컨테이너 이미지는 여러 layer 로 구성되어 있어서 이를 공유하거나 재사용 할 수 있다. 같은 layer 가 필요한 다른 서비스에서 그대로 가져다 쓸 수 있게 된다.
(실제로 Dockerfile 명령 줄 별로 이미지 layer 를 구성하던 것 같다. Hash 를 떠두고 만들어 둔 이미지가 있으면 새로 만들지 않고 그대로 가져와서 쓰던 기억이 있다.) (실제로 Dockerfile 명령 줄 별로 이미지 layer 를 구성하던 것 같다. Hash 를 떠두고 만들어 둔 이미지가 있으면 새로 만들지 않고 그대로 가져와서 쓰던 기억이 있다.)
공유하는 대신 이미지 layer 는 read-only 이다. 컨테이너가 실행되면 writable layer 가 layer 의 맨 위에 생겨서 해당 layer 에 필요한 작업을 하게 된다. 공유하는 대신 이미지 layer 는 read-only 이다. 컨테이너가 실행되면 writable layer 가 layer 의 맨 위에 생겨서 해당 layer 에 필요한 작업을 하게 된다.
#### 도커의 3가지 주요 개념 #### 도커의 3가지 주요 개념
- **이미지**(Images): 애플리케이션과 애플리케이션이 동작하는 환경을 패키징한 것이다. '환경'에는 파일시스템이나, 실행 파일들의 경로와 같은 메타데이터도 들어간다. - **이미지**(Images): 애플리케이션과 애플리케이션이 동작하는 환경을 패키징한 것이다. '환경'에는 파일시스템이나, 실행 파일들의 경로와 같은 메타데이터도 들어간다.
- **레지스트리**(Registries): 도커 이미지의 저장소라고 생각하면 쉽다. 이미지를 만들어서 저장소에 push 할 수 있고, 직접 만든 혹은 만들어진 공식 이미지들을 pull 해서 사용할 수 있다. - **레지스트리**(Registries): 도커 이미지의 저장소라고 생각하면 쉽다. 이미지를 만들어서 저장소에 push 할 수 있고, 직접 만든 혹은 만들어진 공식 이미지들을 pull 해서 사용할 수 있다.
- **컨테이너**(Containers): 이미지로부터 만들어진 컨테이너로 호스트에서 돌아가는 하나의 프로세스이지만, 리눅스 컨테이너 기술이 적용되어 있어 독립적인 프로세스처럼 돌아간다. - **컨테이너**(Containers): 이미지로부터 만들어진 컨테이너로 호스트에서 돌아가는 하나의 프로세스이지만, 리눅스 컨테이너 기술이 적용되어 있어 독립적인 프로세스처럼 돌아간다.
#### 컨테이너 이미지의 한계 #### 컨테이너 이미지의 한계
모든 컨테이너는 호스트의 머신에서 돌아가는 프로세스이기 때문에 해당 컨테이너가 요구하는 커널 버전이나 운영체제, 아키텍쳐 (x86)가 있다면 해당 조건이 만족 되어야 컨테이너를 실행할 수 있다. M1 맥에서 도커 지원을 위해 추가 개발이 필요한 이유이다. 모든 컨테이너는 호스트의 머신에서 돌아가는 프로세스이기 때문에 해당 컨테이너가 요구하는 커널 버전이나 운영체제, 아키텍쳐 (x86)가 있다면 해당 조건이 만족 되어야 컨테이너를 실행할 수 있다. M1 맥에서 도커 지원을 위해 추가 개발이 필요한 이유이다.
VM은 자체적으로 OS를 가지고 있기 때문에 VM을 사용하게 되면 이런 문제가 없다. VM은 자체적으로 OS를 가지고 있기 때문에 VM을 사용하게 되면 이런 문제가 없다.
### 1.2.3 rkt (rock-it) ### 1.2.3 rkt (rock-it)
도커와 같은 컨테이너 엔진이다. 도커와 같은 컨테이너 엔진이다.
보안, 결합성, 표준을 지키는 것에 조금 더 중점을 둔다. 보안, 결합성, 표준을 지키는 것에 조금 더 중점을 둔다.
## 1.3 쿠버네티스 소개 ## 1.3 쿠버네티스 소개
--- ---
마이크로서비스를 스케일링 하고 컨테이너가 많아지면 운영이 어려워진다! 이를 효율적으로 할 수 있게 해줄 도구가 필요하다. 마이크로서비스를 스케일링 하고 컨테이너가 많아지면 운영이 어려워진다! 이를 효율적으로 할 수 있게 해줄 도구가 필요하다.
### 1.3.1 쿠버네티스의 기원 ### 1.3.1 쿠버네티스의 기원
구글에서 Borg, Omega 와 같은 서비스 관리 시스템을 자체 개발하여 사용하다가 2014년에 쿠버네티스를 공개했다. 구글에서 Borg, Omega 와 같은 서비스 관리 시스템을 자체 개발하여 사용하다가 2014년에 쿠버네티스를 공개했다.
### 1.3.2 High-level에서 바라본 쿠버네티스 ### 1.3.2 High-level에서 바라본 쿠버네티스
쿠버네티스는 컨테이너화 된 애플리케이션을 쉽게 배포하고 운영하게 해주는 소프트웨어이다. 쿠버네티스는 컨테이너화 된 애플리케이션을 쉽게 배포하고 운영하게 해주는 소프트웨어이다.
수많은 노드에서 돌아가는 애플리케이션이 마치 한 컴퓨터에서 돌아가는 것처럼 운영해준다. 수많은 노드에서 돌아가는 애플리케이션이 마치 한 컴퓨터에서 돌아가는 것처럼 운영해준다.
클러스터를 위한 운영체제로 생각할 수 있다. 클러스터를 위한 운영체제로 생각할 수 있다.
개발자의 입장에서는 인프라 관련된 기능들을 애플리케이션에 덜 넣어도 된다. (서비스가 준비되었는지 알아낸다거나, 스케일링, 로드 밸런싱, 에러로부터 회복, leader election 등) 개발자는 이제 애플리케이션의 기능에 더욱 집중할 수 있다. 인프라와 통합하는데 개발 리소스를 투입하지 않아도 된다. 개발자의 입장에서는 인프라 관련된 기능들을 애플리케이션에 덜 넣어도 된다. (서비스가 준비되었는지 알아낸다거나, 스케일링, 로드 밸런싱, 에러로부터 회복, leader election 등) 개발자는 이제 애플리케이션의 기능에 더욱 집중할 수 있다. 인프라와 통합하는데 개발 리소스를 투입하지 않아도 된다.
쿠버네티스를 사용하면 사람이 직접 컨테이너와 작업을 스케쥴링 하는 것보다 자원을 효율적으로 사용할 수 있게 된다. 쿠버네티스를 사용하면 사람이 직접 컨테이너와 작업을 스케쥴링 하는 것보다 자원을 효율적으로 사용할 수 있게 된다.
### 1.3.3 쿠버네티스 클러스터 구조 ### 1.3.3 쿠버네티스 클러스터 구조
쿠버네티스 시스템에는 **마스터 노드**(master node)와 **워커 노드**(worker node)가 있다. 쿠버네티스 시스템에는 **마스터 노드**(master node)와 **워커 노드**(worker node)가 있다.
- 마스터 노드: Kubernetes Control Plane 이 실행되고, 쿠버네티스 시스템을 관리 - 마스터 노드: Kubernetes Control Plane 이 실행되고, 쿠버네티스 시스템을 관리
- 워커 노드: 배포한 애플리케이션이 동작하는 노드 - 워커 노드: 배포한 애플리케이션이 동작하는 노드
#### Kubernetes Control Plane #### Kubernetes Control Plane
클러스터를 제어하고 동작하게 한다. 클러스터를 제어하고 동작하게 한다.
- **Kubernetes API 서버**: Control Plane 내부의 각 부분이 서로 통신할 때 사용하는 API 서버이다. 워커 노드에서 API를 이용하기도 한다. - **Kubernetes API 서버**: Control Plane 내부의 각 부분이 서로 통신할 때 사용하는 API 서버이다. 워커 노드에서 API를 이용하기도 한다.
- **스케쥴러**: 애플리케이션 배포시 워커 노드에 서비스를 할당한다. - **스케쥴러**: 애플리케이션 배포시 워커 노드에 서비스를 할당한다.
- **Controller Manager**: 클러스터 수준의 기능을 담당한다. 컴포넌트를 복제하거나 워커 노드 개수를 관리하거나, 노드 에러를 처리하는 등 작업을 담당한다. - **Controller Manager**: 클러스터 수준의 기능을 담당한다. 컴포넌트를 복제하거나 워커 노드 개수를 관리하거나, 노드 에러를 처리하는 등 작업을 담당한다.
- **etcd**: 클러스터의 설정을 저장하는 persistent 분산 데이터 스토어이다. - **etcd**: 클러스터의 설정을 저장하는 persistent 분산 데이터 스토어이다.
#### Worker Nodes #### Worker Nodes
- **Container runtime**: 컨테이너를 실행해준다. - **Container runtime**: 컨테이너를 실행해준다.
- **Kubelet**: API 서버와 통신하고 노드의 컨테이너를 관리한다. - **Kubelet**: API 서버와 통신하고 노드의 컨테이너를 관리한다.
- **Kubernetes Service Proxy** (kube-proxy): 애플리케이션 컴포넌트 간 네트워크 로드 밸런싱을 담당한다. - **Kubernetes Service Proxy** (kube-proxy): 애플리케이션 컴포넌트 간 네트워크 로드 밸런싱을 담당한다.
### 1.3.4 쿠버네티스에서 애플리케이션 실행 ### 1.3.4 쿠버네티스에서 애플리케이션 실행
우선 컨테이너 이미지를 만들어서 이미지 레지스트리에 등록해둬야 한다. 우선 컨테이너 이미지를 만들어서 이미지 레지스트리에 등록해둬야 한다.
그리고 Kubernetes API 서버에 애플리케이션의 구성 명세를 알려줘야 한다. 그리고 Kubernetes API 서버에 애플리케이션의 구성 명세를 알려줘야 한다.
명세에는 애플리케이션의 컴포넌트들에 대한 정보, 각 컴포넌트들의 관계, 같은 노드에 실행되어야 하는 컴포넌트들, 컴포넌트의 복제본 개수, 외부로 공개할 컴포넌트 등에 대한 여러 정보가 들어있다. 명세에는 애플리케이션의 컴포넌트들에 대한 정보, 각 컴포넌트들의 관계, 같은 노드에 실행되어야 하는 컴포넌트들, 컴포넌트의 복제본 개수, 외부로 공개할 컴포넌트 등에 대한 여러 정보가 들어있다.
**API 서버**가 애플리케이션의 구성 명세를 처리하면, **스케줄러**는 컨테이너들을 적절하게 노드에 할당한다. **Kubelet**은 **Container Runtime** 에게 필요한 이미지를 가져와 실행하라고 한다. **API 서버**가 애플리케이션의 구성 명세를 처리하면, **스케줄러**는 컨테이너들을 적절하게 노드에 할당한다. **Kubelet**은 **Container Runtime** 에게 필요한 이미지를 가져와 실행하라고 한다.
컴포넌트의 복제본 개수(replica)를 정해두면 이 값을 지키도록 쿠버네티스가 자동으로 동작한다. 만약 개수가 적으면 더 실행하고, 많으면 줄인다. 또 들어오는 요청이 많거나, CPU 부하가 심할 때와 같이 상황에 맞게 자연스럽게 스케일링도 해준다. 이렇게 하나의 클러스터 내에서 컨테이너를 옮겨다니며 리소스를 효율적으로 쓰기도 하고 관리를 편하게 해준다. 컴포넌트의 복제본 개수(replica)를 정해두면 이 값을 지키도록 쿠버네티스가 자동으로 동작한다. 만약 개수가 적으면 더 실행하고, 많으면 줄인다. 또 들어오는 요청이 많거나, CPU 부하가 심할 때와 같이 상황에 맞게 자연스럽게 스케일링도 해준다. 이렇게 하나의 클러스터 내에서 컨테이너를 옮겨다니며 리소스를 효율적으로 쓰기도 하고 관리를 편하게 해준다.
**kube-proxy** 의 경우 하나의 고정된 IP 주소로 들어오는 요청에 대해 로드 밸런싱을 수행하여 클러스터 내의 적절한 컨테이너에게 작업을 넘겨준다. **kube-proxy** 의 경우 하나의 고정된 IP 주소로 들어오는 요청에 대해 로드 밸런싱을 수행하여 클러스터 내의 적절한 컨테이너에게 작업을 넘겨준다.
### 1.3.5 쿠버네티스 사용의 장점 ### 1.3.5 쿠버네티스 사용의 장점
개발자들은 클러스터를 구성하는 서버에 대해서 잘 몰라도 된다. 추상화로 인해 모든 노드/클러스터가 커다란 서버가 된다. 개발자들은 클러스터를 구성하는 서버에 대해서 잘 몰라도 된다. 추상화로 인해 모든 노드/클러스터가 커다란 서버가 된다.
물론 서버에 GPU가 필요하거나 추가적인 제약 조건이 따른다면 이를 잘 조절해 줘야 한다. (AWS GPU 인스턴스, DB 인스턴스 등) 물론 서버에 GPU가 필요하거나 추가적인 제약 조건이 따른다면 이를 잘 조절해 줘야 한다. (AWS GPU 인스턴스, DB 인스턴스 등)
쿠버네티스를 사용하게 되면 애플리케이션과 인프라가 분리된다! 무엇보다 개발 환경과 배포 환경을 유사하게 설정할 수 있어서 편해진다. 쿠버네티스를 사용하게 되면 애플리케이션과 인프라가 분리된다! 무엇보다 개발 환경과 배포 환경을 유사하게 설정할 수 있어서 편해진다.
쿠버네티스가 하드웨어 리소스를 효율적으로 사용할 수 있도록 도와준다. 컴포넌트들을 관리하며 스케쥴링을 해주고 오류 발생시 자동으로 처리해주고 (노드 재시작 등), 부하가 심한 경우 자동으로 스케일링을 제공한다. 쿠버네티스가 하드웨어 리소스를 효율적으로 사용할 수 있도록 도와준다. 컴포넌트들을 관리하며 스케쥴링을 해주고 오류 발생시 자동으로 처리해주고 (노드 재시작 등), 부하가 심한 경우 자동으로 스케일링을 제공한다.
새로운 버전의 애플리케이션을 배포할 때 연속적인 배포를 할 수 있게 된다. 중간에 서비스를 중단하지 않아도 된다. 새로운 버전의 애플리케이션을 배포할 때 연속적인 배포를 할 수 있게 된다. 중간에 서비스를 중단하지 않아도 된다.
--- ---
[^1]: 물론 컨테이너를 씀으로 인해 발생하는 새로운 문제를 얻겠지만, 개인적으로 장점이 더 크다고 생각한다. [^1]: 물론 컨테이너를 씀으로 인해 발생하는 새로운 문제를 얻겠지만, 개인적으로 장점이 더 크다고 생각한다.

View File

@@ -1,314 +1,314 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops, docker] tags: [kubernetes, sre, devops, docker]
title: "02. First Steps with Docker and Kubernetes" title: "02. First Steps with Docker and Kubernetes"
date: "2021-03-07" date: "2021-03-07"
github_title: "2021-03-07-02-first-steps-with-docker-and-kubernetes" github_title: "2021-03-07-02-first-steps"
image: image:
path: /assets/img/posts/k8s-02.jpeg path: /assets/img/posts/k8s-02.jpeg
--- ---
![k8s-02.jpeg](../../../assets/img/posts/k8s-02.jpeg) _Running a container image in Kubernetes (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-2)_ ![k8s-02.jpeg](../../../assets/img/posts/k8s-02.jpeg) _Running a container image in Kubernetes (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-2)_
도커와 쿠버네티스를 사용하여 간단한 애플리케이션을 배포해 보자! 도커와 쿠버네티스를 사용하여 간단한 애플리케이션을 배포해 보자!
## 2.1 컨테이너 이미지 생성, 실행, 공유하기 ## 2.1 컨테이너 이미지 생성, 실행, 공유하기
--- ---
### 2.1.1 도커 설치 ### 2.1.1 도커 설치
도커 공식 홈페이지에 있는 대로 설치한다! 도커 공식 홈페이지에 있는 대로 설치한다!
- Mac 이나 Windows 를 쓰면 도커가 VM을 세팅하고 VM 안에서 도커 데몬이 실행된다. - Mac 이나 Windows 를 쓰면 도커가 VM을 세팅하고 VM 안에서 도커 데몬이 실행된다.
#### 컨테이너 실행 #### 컨테이너 실행
```bash ```bash
$ docker run <IMAGE>:[TAG] [COMMAND] $ docker run <IMAGE>:[TAG] [COMMAND]
``` ```
- 이미지를 다운받고 실행해주는 명령 - 이미지를 다운받고 실행해주는 명령
예시로 `docker run busybox echo "Hello world"` 가 있는데, 별거 아닌 것 같아 보여도 한 줄로 앱이 통째로 설치되고 실행이 되고 있기 때문에 생각보다 대단한 것이다! 예시로 `docker run busybox echo "Hello world"` 가 있는데, 별거 아닌 것 같아 보여도 한 줄로 앱이 통째로 설치되고 실행이 되고 있기 때문에 생각보다 대단한 것이다!
#### `docker run` 이 실행될 때... #### `docker run` 이 실행될 때...
- 우선 해당 이미지의 latest version (`IMAGE:latest`) 가 로컬에 있는지 확인한다. 없으면 Docker Hub 에서 다운받는다. - 우선 해당 이미지의 latest version (`IMAGE:latest`) 가 로컬에 있는지 확인한다. 없으면 Docker Hub 에서 다운받는다.
- 이미지로부터 컨테이너를 생성하고 `[COMMAND]` 를 실행한다. - 이미지로부터 컨테이너를 생성하고 `[COMMAND]` 를 실행한다.
매 번 `docker run` 을 할 때마다 실행할 명령을 입력하는 것은 불편하므로 보통은 Dockerfile 을 사용해서 실행할 명령을 이미지 생성 과정에 포함시킨다. 매 번 `docker run` 을 할 때마다 실행할 명령을 입력하는 것은 불편하므로 보통은 Dockerfile 을 사용해서 실행할 명령을 이미지 생성 과정에 포함시킨다.
이미지도 업데이트 될 수 있기 때문에, 이름은 유지한 채 tag 를 업데이트하여 버전을 관리한다. `latest` 태그는 당연히 이미지의 최신 버전을 가리킨다. 이미지도 업데이트 될 수 있기 때문에, 이름은 유지한 채 tag 를 업데이트하여 버전을 관리한다. `latest` 태그는 당연히 이미지의 최신 버전을 가리킨다.
### 2.1.2 Creating a trivial Node.js app ### 2.1.2 Creating a trivial Node.js app
HTTP 요청을 받으면 실행 중인 머신의 hostname 을 포함하여 응답을 돌려주는 앱을 node.js 로 만들고 Docker 이미지로 패키징 한다. HTTP 요청을 받으면 실행 중인 머신의 hostname 을 포함하여 응답을 돌려주는 앱을 node.js 로 만들고 Docker 이미지로 패키징 한다.
패키징 후 컨테이너를 실행하여 HTTP 요청을 날려보면 응답에 포함된 hostname 을 확인하면 컨테이너 내에서 독립적인 hostname 을 갖는다는 것을 확인할 수 있을 것이다. 패키징 후 컨테이너를 실행하여 HTTP 요청을 날려보면 응답에 포함된 hostname 을 확인하면 컨테이너 내에서 독립적인 hostname 을 갖는다는 것을 확인할 수 있을 것이다.
(Docker 가 실행 중인 머신의 hostname 이 아닌 다른 결과가 나올 것) (Docker 가 실행 중인 머신의 hostname 이 아닌 다른 결과가 나올 것)
### 2.1.3 Dockerfile 만들기 ### 2.1.3 Dockerfile 만들기
애플리케이션을 이미지로 패키징하려면 Dockerfile 에 이미지를 만들 때 실행해야 하는 명령들을 넣어주면 된다. 애플리케이션을 이미지로 패키징하려면 Dockerfile 에 이미지를 만들 때 실행해야 하는 명령들을 넣어주면 된다.
- `FROM` : 베이스 이미지로 사용할 이미지를 정한다. `FROM ubuntu:20.04` 라고 하면 ubuntu 20.04 환경에서 시작한다. - `FROM` : 베이스 이미지로 사용할 이미지를 정한다. `FROM ubuntu:20.04` 라고 하면 ubuntu 20.04 환경에서 시작한다.
- `ADD <src> <dst>` : 로컬의 `src` 경로의 파일을 컨테이너 내 `dst` 경로로 복사한다. - `ADD <src> <dst>` : 로컬의 `src` 경로의 파일을 컨테이너 내 `dst` 경로로 복사한다.
- `ENTRYPOINT ["executable", "param1", "param2"]` : 컨테이너 내부 shell 에서 `$ executable param1 param2` 를 실행하는 것과 동일하다. 다만 Dockerfile 전체에서 가장 마지막 `ENTRYPOINT` 명령만 실행되므로 한 번만 사용할 수 있다. - `ENTRYPOINT ["executable", "param1", "param2"]` : 컨테이너 내부 shell 에서 `$ executable param1 param2` 를 실행하는 것과 동일하다. 다만 Dockerfile 전체에서 가장 마지막 `ENTRYPOINT` 명령만 실행되므로 한 번만 사용할 수 있다.
### 2.1.4 컨테이너 이미지 빌드 ### 2.1.4 컨테이너 이미지 빌드
'이미지를 빌드한다'는 것은 Dockerfile 에 있는 명령을 실행한다고 생각하면 될 것이다. 물론 Dockerfile 없이 빌드 할 수 있다. 깃헙에서 commit changes 하듯이, 컨테이너 내부에서 명령을 직접 실행한 뒤 컨테이너의 상태를 저장 할 수 있다. 하지만 당연히 직접 실행하는 것보다는 파일로 관리하는 것이 편할 것이다. '이미지를 빌드한다'는 것은 Dockerfile 에 있는 명령을 실행한다고 생각하면 될 것이다. 물론 Dockerfile 없이 빌드 할 수 있다. 깃헙에서 commit changes 하듯이, 컨테이너 내부에서 명령을 직접 실행한 뒤 컨테이너의 상태를 저장 할 수 있다. 하지만 당연히 직접 실행하는 것보다는 파일로 관리하는 것이 편할 것이다.
```bash ```bash
$ docker build -t <TAG> <PATH> $ docker build -t <TAG> <PATH>
``` ```
- `-t` 옵션은 이미지의 태그를 지정해준다. - `-t` 옵션은 이미지의 태그를 지정해준다.
- `PATH` 에 있는 Dockerfile 을 이용하여 이미지를 빌드한다. - `PATH` 에 있는 Dockerfile 을 이용하여 이미지를 빌드한다.
이미지 빌드시 `PATH` 의 모든 파일이 도커 데몬에 옮겨진 다음 그 곳에서 이미지를 빌드한다. 이미지 빌드시 `PATH` 의 모든 파일이 도커 데몬에 옮겨진 다음 그 곳에서 이미지를 빌드한다.
#### 이미지 레이어 #### 이미지 레이어
이미지는 여러 레이어로 구성이 되어있다. `docker build` 를 해보면 step 별로 진행 되는 것을 볼 수 있는데 각 step 마다 layer 가 하나 생성된다. Dockerfile 명령 한 줄마다 layer 하나를 쓰게 된다. 이미지는 여러 레이어로 구성이 되어있다. `docker build` 를 해보면 step 별로 진행 되는 것을 볼 수 있는데 각 step 마다 layer 가 하나 생성된다. Dockerfile 명령 한 줄마다 layer 하나를 쓰게 된다.
다만 `&&` 를 사용해서 Dockerfile 의 line 수를 줄이면, 나중에 명령을 수정할 때 만들어 둔 layer 를 재사용하지 못하게 되는 경우가 있다. 그러면 이미지 빌드가 느려진다. 다만 `&&` 를 사용해서 Dockerfile 의 line 수를 줄이면, 나중에 명령을 수정할 때 만들어 둔 layer 를 재사용하지 못하게 되는 경우가 있다. 그러면 이미지 빌드가 느려진다.
이미지는 용량을 많이 차지하게 될 수 있으므로 한 번만 저장하고 같은 레이어는 재사용하게 된다. 이미지는 용량을 많이 차지하게 될 수 있으므로 한 번만 저장하고 같은 레이어는 재사용하게 된다.
```bash ```bash
$ docker images $ docker images
``` ```
- 로컬에 있는 이미지 목록을 보여준다. - 로컬에 있는 이미지 목록을 보여준다.
### 2.1.5 컨테이너 실행 ### 2.1.5 컨테이너 실행
```bash ```bash
$ docker run [OPTIONS] <IMAGE> # 기본형 $ docker run [OPTIONS] <IMAGE> # 기본형
$ docker run --name <CONTAINER_NAME> -p <HOST_PORT>:<CONTAINER_PORT> -d <IMAGE> $ docker run --name <CONTAINER_NAME> -p <HOST_PORT>:<CONTAINER_PORT> -d <IMAGE>
``` ```
- `IMAGE` 로부터 컨테이너를 실행한다. - `IMAGE` 로부터 컨테이너를 실행한다.
- `--name` 옵션은 실행된 컨테이너에 `CONTAINER_NAME` 으로 이름을 부여한다. - `--name` 옵션은 실행된 컨테이너에 `CONTAINER_NAME` 으로 이름을 부여한다.
- `-p` (`--publish`) 옵션은 호스트의 포트를 이용하여 컨테이너의 포트에 접근할 수 있도록 한다. 컨테이너의 포트를 호스트 포트로 publish 한다. - `-p` (`--publish`) 옵션은 호스트의 포트를 이용하여 컨테이너의 포트에 접근할 수 있도록 한다. 컨테이너의 포트를 호스트 포트로 publish 한다.
- `-d` (`--detach`) 옵션은 background 에서 작업이 수행되도록 한다. - `-d` (`--detach`) 옵션은 background 에서 작업이 수행되도록 한다.
```bash ```bash
$ docker ps $ docker ps
``` ```
- 실행 중인 컨테이너의 목록과 간략한 정보를 확인한다. - 실행 중인 컨테이너의 목록과 간략한 정보를 확인한다.
- 컨테이너 ID, 이름, 컨테이너 생성에 사용된 이미지, 컨테이너 내부에서 실행 중인 명령을 확인할 수 있다. - 컨테이너 ID, 이름, 컨테이너 생성에 사용된 이미지, 컨테이너 내부에서 실행 중인 명령을 확인할 수 있다.
- 자세히 보고 싶으면 `docker inspect <CONTAINER>` 를 사용하여 JSON 으로 자세한 정보를 확인한다. - 자세히 보고 싶으면 `docker inspect <CONTAINER>` 를 사용하여 JSON 으로 자세한 정보를 확인한다.
### 2.1.6 컨테이너 내부 들여다보기 ### 2.1.6 컨테이너 내부 들여다보기
안에서 명령을 실행할 일이 분명 생긴다. 안에서 명령을 실행할 일이 분명 생긴다.
```bash ```bash
$ docker exec [OPTIONS] <CONTAINER> <COMMAND> [ARGS] # 기본형 $ docker exec [OPTIONS] <CONTAINER> <COMMAND> [ARGS] # 기본형
$ docker exec -it <CONTAINER> <COMMAND> $ docker exec -it <CONTAINER> <COMMAND>
``` ```
- `exec``CONTAINER` 내에서 `COMMAND` 를 실행하라는 의미이다. - `exec``CONTAINER` 내에서 `COMMAND` 를 실행하라는 의미이다.
- `-i` (`--interactive`) 는 STDIN (표준 입력)을 열어준다. - `-i` (`--interactive`) 는 STDIN (표준 입력)을 열어준다.
- `-t` (`--tty`) 는 pseudo-TTY (유사 터미널)을 열어준다. - `-t` (`--tty`) 는 pseudo-TTY (유사 터미널)을 열어준다.
그러므로 `docker exec -it <CONTAINER> bash` 를 하게 되면 컨테이너 내부에서 `bash` 명령이 터미널 내에서 실행되고, 표준 입력이 연결되어 있으므로 명령을 입력할 수 있게 된다. (쉘을 딸 수 있다는 것이다) 그러므로 `docker exec -it <CONTAINER> bash` 를 하게 되면 컨테이너 내부에서 `bash` 명령이 터미널 내에서 실행되고, 표준 입력이 연결되어 있으므로 명령을 입력할 수 있게 된다. (쉘을 딸 수 있다는 것이다)
#### 독립된 공간 #### 독립된 공간
컨테이너 내에서 `ps aux` 를 입력하면 호스트 머신에서 돌아가는 프로세스들은 보이지 않는다. 한편 호스트 머신에서 `ps aux` 를 입력한후 `grep` 으로 프로세스를 찾아보면 컨테이너 내에서 동작 중인 프로세스가 목록에 있음을 확인할 수 있다. 컨테이너 내에서 `ps aux` 를 입력하면 호스트 머신에서 돌아가는 프로세스들은 보이지 않는다. 한편 호스트 머신에서 `ps aux` 를 입력한후 `grep` 으로 프로세스를 찾아보면 컨테이너 내에서 동작 중인 프로세스가 목록에 있음을 확인할 수 있다.
즉 독립적인 공간에서 실행되는 것처럼 환경을 제공하지만, 실제로는 호스트의 프로세스인 것임을 확인할 수 있다. 즉 독립적인 공간에서 실행되는 것처럼 환경을 제공하지만, 실제로는 호스트의 프로세스인 것임을 확인할 수 있다.
마찬가지로 `ls` 명령을 쳐보면 내부에 독립적인 파일시스템을 갖고 있음을 확인할 수 있다. (물론 Dockerfile 에서 직접 추가한 파일이 있을 수도 있고, 특정 디렉토리를 마운트 한 경우 공유가 되기도 한다.) 마찬가지로 `ls` 명령을 쳐보면 내부에 독립적인 파일시스템을 갖고 있음을 확인할 수 있다. (물론 Dockerfile 에서 직접 추가한 파일이 있을 수도 있고, 특정 디렉토리를 마운트 한 경우 공유가 되기도 한다.)
### 2.1.7 컨테이너 정지 및 삭제 ### 2.1.7 컨테이너 정지 및 삭제
```bash ```bash
$ docker stop <CONTAINER> # 정지 $ docker stop <CONTAINER> # 정지
$ docker rm <CONTAINER> # 삭제 $ docker rm <CONTAINER> # 삭제
``` ```
컨테이너를 정지하게 되면 그 잔해가 남아있게 되는데, `rm` 을 이용해 삭제해주면 완전히 제거된다. 컨테이너를 정지하게 되면 그 잔해가 남아있게 되는데, `rm` 을 이용해 삭제해주면 완전히 제거된다.
정지한 컨테이너는 `docker ps` 로 확인이 불가능하므로 (실행 중인 컨테이너만 보인다), `docker ps -a` 로 확인한다. 정지한 컨테이너는 `docker ps` 로 확인이 불가능하므로 (실행 중인 컨테이너만 보인다), `docker ps -a` 로 확인한다.
### 2.1.8 이미지 레지스트리에 이미지 push 하기 ### 2.1.8 이미지 레지스트리에 이미지 push 하기
주의사항: Docker Hub 에 push 하려면 이미지의 레포지토리 이름이 Docker Hub ID 로 시작해야 한다. 주의사항: Docker Hub 에 push 하려면 이미지의 레포지토리 이름이 Docker Hub ID 로 시작해야 한다.
#### 이미지 태그 #### 이미지 태그
```bash ```bash
$ docker tag <SOURCE_IMAGE:TAG> <TARGET_IMAGE:TAG> $ docker tag <SOURCE_IMAGE:TAG> <TARGET_IMAGE:TAG>
``` ```
- 새로운 태그 `TARGET_IMAGE` 를 만들어서 `SOURCE_IMAGE` 를 가리키게 한다. - 새로운 태그 `TARGET_IMAGE` 를 만들어서 `SOURCE_IMAGE` 를 가리키게 한다.
- Alias 를 만든다. 이미지를 복사하는 것이 아니다. `docker images` 로 확인하면 `SOURCE_IMAGE` 가 이름이 바뀐게 아니라 새로 생겼음을 확인할 수 있다. (메모리 낭비 금지) - Alias 를 만든다. 이미지를 복사하는 것이 아니다. `docker images` 로 확인하면 `SOURCE_IMAGE` 가 이름이 바뀐게 아니라 새로 생겼음을 확인할 수 있다. (메모리 낭비 금지)
`docker login` 으로 로그인 한 뒤, `docker push <NAME:TAG>` 으로 이미지를 push 할 수 있다. 당연히 그 반대인 `docker pull` 도 가능하다. `docker login` 으로 로그인 한 뒤, `docker push <NAME:TAG>` 으로 이미지를 push 할 수 있다. 당연히 그 반대인 `docker pull` 도 가능하다.
## 2.2 쿠버네티스 클러스터 설정 ## 2.2 쿠버네티스 클러스터 설정
--- ---
일단 로컬에서 하려면 Minikube 를 설치하면 된다. 일단 로컬에서 하려면 Minikube 를 설치하면 된다.
### 2.2.1 Minikube 를 이용한 단일 노드 클러스터 ### 2.2.1 Minikube 를 이용한 단일 노드 클러스터
- Minikube 를 설치하고, `kubectl` 을 설치하면 된다. - Minikube 를 설치하고, `kubectl` 을 설치하면 된다.
- `minikube start` 하고 `kubectl cluster-info` 하면 클러스터가 생성된 것을 확인할 수 있다. - `minikube start` 하고 `kubectl cluster-info` 하면 클러스터가 생성된 것을 확인할 수 있다.
개인적으로 oh-my-zsh 를 사용하는데 `kubectl` 까지 치면 옆에 현재 context 가 나와서 참 좋다. 개인적으로 oh-my-zsh 를 사용하는데 `kubectl` 까지 치면 옆에 현재 context 가 나와서 참 좋다.
### 2.2.2 Google Kubernetes Engine (GKE) ### 2.2.2 Google Kubernetes Engine (GKE)
- 세팅은 패스 (요새 무료 크레딧도 주던데...) - 세팅은 패스 (요새 무료 크레딧도 주던데...)
```bash ```bash
$ gcloud container clusters create <NAME> --num-nodes <NUM> --machine-type <MACHINE_TYPE> $ gcloud container clusters create <NAME> --num-nodes <NUM> --machine-type <MACHINE_TYPE>
``` ```
- 쿠버네티스 클러스터 (노드의 집합)를 생성한다. - 쿠버네티스 클러스터 (노드의 집합)를 생성한다.
- 클러스터 내에 몇 개의 노드가 있는지 설정할 수 있다. - 클러스터 내에 몇 개의 노드가 있는지 설정할 수 있다.
- `--machine-type` 옵션으로 얼마나 비싼 기계를 쓸지 고를 수 있다. - `--machine-type` 옵션으로 얼마나 비싼 기계를 쓸지 고를 수 있다.
각 worker 노드에서는 Docker, Kubelet, kube-proxy 가 실행되고 있고, 마스터 노드가 이들을 관리하게 된다. 각 worker 노드에서는 Docker, Kubelet, kube-proxy 가 실행되고 있고, 마스터 노드가 이들을 관리하게 된다.
`kubectl` 을 이용하면 마스터 노드에 있는 REST API 서버를 호출하여 작업하게 된다. `kubectl` 을 이용하면 마스터 노드에 있는 REST API 서버를 호출하여 작업하게 된다.
노드를 생성했으니 노드의 목록을 확인하는 방법을 알아보자. 노드를 생성했으니 노드의 목록을 확인하는 방법을 알아보자.
```bash ```bash
$ kubectl get nodes $ kubectl get nodes
``` ```
- 노드 목록을 보여준다. - 노드 목록을 보여준다.
사실 `kubectl get` 은 docs 에도 나와 있듯이 '하나 이상의 리소스를 나열'한다. 더 자세히 보고싶으면, 사실 `kubectl get` 은 docs 에도 나와 있듯이 '하나 이상의 리소스를 나열'한다. 더 자세히 보고싶으면,
```bash ```bash
$ kubectl describe node <NAME> $ kubectl describe node <NAME>
``` ```
으로 확인하면 된다. 으로 확인하면 된다.
### 2.2.3 Alias for `kubectl` ### 2.2.3 Alias for `kubectl`
솔직히 `kubectl` 매번 입력하기 귀찮다. 적당히 자주 쓰는 명령 alias 해둔다. 솔직히 `kubectl` 매번 입력하기 귀찮다. 적당히 자주 쓰는 명령 alias 해둔다.
`bash-completion` 을 사용하면 편리하다고 한다. `bash-completion` 을 사용하면 편리하다고 한다.
## 2.3 Kubernetes 에서 앱 실행하기 ## 2.3 Kubernetes 에서 앱 실행하기
--- ---
### 2.3.1 Node.js 앱 배포하기 ### 2.3.1 Node.js 앱 배포하기
```bash ```bash
$ kubectl run [NAME] --image=[IMAGE] --port=[PORT] --generator=run/v1 $ kubectl run [NAME] --image=[IMAGE] --port=[PORT] --generator=run/v1
``` ```
- 설정 파일 (`.yaml`) 없이도 실행할 수 있게 해준다. - 설정 파일 (`.yaml`) 없이도 실행할 수 있게 해준다.
- `--image` 옵션으로 사용할 이미지를 설정할 수 있다. - `--image` 옵션으로 사용할 이미지를 설정할 수 있다.
- `--port` 옵션으로 포트를 개방할 수 있다. - `--port` 옵션으로 포트를 개방할 수 있다.
- `--generator`*ReplicationController* 를 생성하기 위함이라고 한다. - `--generator`*ReplicationController* 를 생성하기 위함이라고 한다.
쿠버네티스는 컨테이너를 개별적으로 다루지는 않는 편이다. 대신 함께 존재하는 여러 컨테이너들을 다룬다. 이러한 컨테이너의 묶음을 **pod** 라고 한다. 쿠버네티스는 컨테이너를 개별적으로 다루지는 않는 편이다. 대신 함께 존재하는 여러 컨테이너들을 다룬다. 이러한 컨테이너의 묶음을 **pod** 라고 한다.
Pod 안에 여러 개의 컨테이너가 존재할 수 있고, 그 컨테이너는 모두 같은 worker 노드에서 실행되며, Linux namespace 를 공유한다. 각 pod 는 마치 개별적인 머신처럼 동작하여 IP, hostname, 프로세스 등을 개별적으로 갖게 된다. Pod 안에 여러 개의 컨테이너가 존재할 수 있고, 그 컨테이너는 모두 같은 worker 노드에서 실행되며, Linux namespace 를 공유한다. 각 pod 는 마치 개별적인 머신처럼 동작하여 IP, hostname, 프로세스 등을 개별적으로 갖게 된다.
당연히, pod 를 가져오는 명령은 당연히, pod 를 가져오는 명령은
```bash ```bash
$ kubectl get pods $ kubectl get pods
``` ```
이다. `READY` 열을 보면 컨테이너가 실행 대기 중인 경우 `0/1` 과 같이 나올 수도 있다. 이다. `READY` 열을 보면 컨테이너가 실행 대기 중인 경우 `0/1` 과 같이 나올 수도 있다.
#### 무슨 일이 일어났나요? #### 무슨 일이 일어났나요?
- `kubectl run` 을 실행하면 Kubernetes API 서버에 REST HTTP 요청을 보낸다. - `kubectl run` 을 실행하면 Kubernetes API 서버에 REST HTTP 요청을 보낸다.
- Kubernetes API 서버는 요청을 받아 새로운 ReplicationController 오브젝트를 생성한다. - Kubernetes API 서버는 요청을 받아 새로운 ReplicationController 오브젝트를 생성한다.
- ReplicationController 가 새로운 pod 를 생성하게 되고, 이는 Scheduler 에 의해 어떤 worker 노드에 할당된다. - ReplicationController 가 새로운 pod 를 생성하게 되고, 이는 Scheduler 에 의해 어떤 worker 노드에 할당된다.
- Kubelet 이 동작하여 Docker 를 실행하게 하고 Docker 가 컨테이너를 실행한다. - Kubelet 이 동작하여 Docker 를 실행하게 하고 Docker 가 컨테이너를 실행한다.
### 2.3.2 애플리케이션에 접근하기 ### 2.3.2 애플리케이션에 접근하기
배포한 앱은 pod 에서 실행되므로 IP가 할당되는데, 이 IP는 클러스터 내부에서 사용되는 IP이므로 외부에서 접속이 불가능하다. Service 오브젝트를 사용하여 외부로 공개해야 한다. 배포한 앱은 pod 에서 실행되므로 IP가 할당되는데, 이 IP는 클러스터 내부에서 사용되는 IP이므로 외부에서 접속이 불가능하다. Service 오브젝트를 사용하여 외부로 공개해야 한다.
`LoadBalancer`-type service 를 생성하면 이 load balancer 의 공개 IP를 이용하여 pod 에 접근할 수 있게 된다. `LoadBalancer`-type service 를 생성하면 이 load balancer 의 공개 IP를 이용하여 pod 에 접근할 수 있게 된다.
```bash ```bash
$ kubectl expose rc <RC_NAME> --type=LoadBalancer --name <SERVICE_NAME> $ kubectl expose rc <RC_NAME> --type=LoadBalancer --name <SERVICE_NAME>
``` ```
- `expose` 에서 외부로 공개한다는 의미를 알 수 있다. - `expose` 에서 외부로 공개한다는 의미를 알 수 있다.
- `rc``ReplicationController` 의 약자다. - `rc``ReplicationController` 의 약자다.
서비스 오브젝트를 생성했다면 서비스 목록을 확인한다. 서비스 오브젝트를 생성했다면 서비스 목록을 확인한다.
```bash ```bash
$ kubectl get services $ kubectl get services
``` ```
- 실행 중인 서비스 목록을 확인한다. - 실행 중인 서비스 목록을 확인한다.
- `EXTERNAL-IP` 에 공개 IP가 적혀있다. 서비스 생성에 시간이 걸릴 수 있으니 IP가 할당되려면 조금 기다려야 할지도? - `EXTERNAL-IP` 에 공개 IP가 적혀있다. 서비스 생성에 시간이 걸릴 수 있으니 IP가 할당되려면 조금 기다려야 할지도?
### 2.3.3 The logical parts of your system ### 2.3.3 The logical parts of your system
#### Pod #### Pod
Kubernetes 의 basic building block 은 pod 이다! Pod 안에는 여러 개의 컨테이너가 실행될 수 있고, pod 는 각각 IP 주소와 hostname 을 갖는다. Kubernetes 의 basic building block 은 pod 이다! Pod 안에는 여러 개의 컨테이너가 실행될 수 있고, pod 는 각각 IP 주소와 hostname 을 갖는다.
#### ReplicationController #### ReplicationController
Pod 를 만든 것은 ReplicationController 인데, 얘는 pod 의 개수를 조절하고 관리한다. Pod 를 한 개 만들라고 했다면 정확히 1개가 유지되도록 한다. 오류가 나서 종료되더라도 다시 시작시켜 준다. 또 서비스가 굉장히 커져서 pod 하나만으로는 트래픽을 감당할 수 없다면, pod 를 여러 개 복제하여 실행하는데 사용할 수 있다. Pod 를 만든 것은 ReplicationController 인데, 얘는 pod 의 개수를 조절하고 관리한다. Pod 를 한 개 만들라고 했다면 정확히 1개가 유지되도록 한다. 오류가 나서 종료되더라도 다시 시작시켜 준다. 또 서비스가 굉장히 커져서 pod 하나만으로는 트래픽을 감당할 수 없다면, pod 를 여러 개 복제하여 실행하는데 사용할 수 있다.
#### Service #### Service
Pod 의 경우 오류가 나거나 로드 밸런싱이나 효율적인 시스템 자원의 활용을 위해 중지되거나 다른 노드로 옮겨가는 등 수시로 생겼다 사라졌다 할 수 있다. 이 때마다 (내부) IP가 바뀌게 되므로 이를 효율적으로 관리해주는 service 가 필요하게 된다. 공개 IP를 하나 설정하여 외부에서 접근이 가능하도록 한다. Pod 의 경우 오류가 나거나 로드 밸런싱이나 효율적인 시스템 자원의 활용을 위해 중지되거나 다른 노드로 옮겨가는 등 수시로 생겼다 사라졌다 할 수 있다. 이 때마다 (내부) IP가 바뀌게 되므로 이를 효율적으로 관리해주는 service 가 필요하게 된다. 공개 IP를 하나 설정하여 외부에서 접근이 가능하도록 한다.
### 2.3.4 Horizontal Scaling ### 2.3.4 Horizontal Scaling
```bash ```bash
$ kubectl get replicationcontorllers $ kubectl get replicationcontorllers
``` ```
- 존재하는 ReplicationController 를 확인한다. - 존재하는 ReplicationController 를 확인한다.
- `DESIRED``CURRENT` 열에서 있어야 하는 pod 개수와 현재 pod 개수를 확인할 수 있다. - `DESIRED``CURRENT` 열에서 있어야 하는 pod 개수와 현재 pod 개수를 확인할 수 있다.
```bash ```bash
$ kubectl scale rc <RC_NAME> --replica=<NUM> $ kubectl scale rc <RC_NAME> --replica=<NUM>
``` ```
- `DESIRED` 값을 `NUM` 으로 설정한다. - `DESIRED` 값을 `NUM` 으로 설정한다.
- 그러면 ReplicationController 가 알아서 개수를 맞춰준다. - 그러면 ReplicationController 가 알아서 개수를 맞춰준다.
변경사항이 있다면 `kubectl get rc``kubectl get pods``DESIRED` / `CURRENT` 값이 변경 되었고 pod 가 실행 중인지 확인하면 된다. Kubernetes 없이 복제하려 했으면 얼마나 번거로웠을까? 변경사항이 있다면 `kubectl get rc``kubectl get pods``DESIRED` / `CURRENT` 값이 변경 되었고 pod 가 실행 중인지 확인하면 된다. Kubernetes 없이 복제하려 했으면 얼마나 번거로웠을까?
위의 node.js 앱을 배포한 상태에서 pod 가 여러 개 생겼으니 외부에서 HTTP 요청을 보내보면, 안에서 load balancing 이 동작하고 있기 때문에 꼭 같은 hostname 을 반환하지는 않는다. 위의 node.js 앱을 배포한 상태에서 pod 가 여러 개 생겼으니 외부에서 HTTP 요청을 보내보면, 안에서 load balancing 이 동작하고 있기 때문에 꼭 같은 hostname 을 반환하지는 않는다.
### 2.3.5 앱이 실행 중인 노드 확인 ### 2.3.5 앱이 실행 중인 노드 확인
보통 어느 노드에서 실행 중인지 중요하지는 않지만 확인할 수 있긴 하다. 보통은 할당된 노드의 자원 (CPU 사용량, 메모리) 등을 확인해야 하긴 한다. 보통 어느 노드에서 실행 중인지 중요하지는 않지만 확인할 수 있긴 하다. 보통은 할당된 노드의 자원 (CPU 사용량, 메모리) 등을 확인해야 하긴 한다.
```bash ```bash
$ kubectl get pods -o wide # 더 많은 정보 출력 $ kubectl get pods -o wide # 더 많은 정보 출력
$ kubectl describe pod <POD_NAME> # 상세 정보 출력 $ kubectl describe pod <POD_NAME> # 상세 정보 출력
``` ```
### 2.3.6 Kubernetes Dashboard ### 2.3.6 Kubernetes Dashboard
GUI 도 지원해 준다. GUI 도 지원해 준다.
```bash ```bash
$ kubectl cluster-info | grep dashboard $ kubectl cluster-info | grep dashboard
``` ```
- 대시보드가 실행 중인 주소를 알려줄 것이다. - 대시보드가 실행 중인 주소를 알려줄 것이다.
- Minikube 의 경우 `minikube dashboard`. - Minikube 의 경우 `minikube dashboard`.

View File

@@ -1,455 +1,455 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops] tags: [kubernetes, sre, devops]
title: "03. Pods: Running Containers in Kubernetes" title: "03. Pods: Running Containers in Kubernetes"
date: "2021-03-17" date: "2021-03-17"
github_title: "2021-03-17-03-pods" github_title: "2021-03-17-03-pods"
image: image:
path: /assets/img/posts/k8s-03.jpeg path: /assets/img/posts/k8s-03.jpeg
--- ---
![k8s-03.jpeg](../../../assets/img/posts/k8s-03.jpeg) _A container shouldnt run multiple processes. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-3)_ ![k8s-03.jpeg](../../../assets/img/posts/k8s-03.jpeg) _A container shouldnt run multiple processes. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-3)_
다양한 쿠버네티스 오브젝트 (resources) 를 살펴보는 단원이다. 가장 기본이 되는 Pod 부터 시작한다. 이외의 모든 것들은 pod 를 관리하거나, pod 를 노출하거나, pod 에 의해 사용된다. 다양한 쿠버네티스 오브젝트 (resources) 를 살펴보는 단원이다. 가장 기본이 되는 Pod 부터 시작한다. 이외의 모든 것들은 pod 를 관리하거나, pod 를 노출하거나, pod 에 의해 사용된다.
## 3.1 Introducing Pods ## 3.1 Introducing Pods
--- ---
**Pod**는 컨테이너의 모임이며, 쿠버네티스의 기본적인 building block 이 된다. 하나 기억할 사실은, 하나의 pod 는 하나의 노드 위에 있게 되므로, pod 내부의 모든 컨테이너는 같은 노드에 존재하게 된다. **Pod**는 컨테이너의 모임이며, 쿠버네티스의 기본적인 building block 이 된다. 하나 기억할 사실은, 하나의 pod 는 하나의 노드 위에 있게 되므로, pod 내부의 모든 컨테이너는 같은 노드에 존재하게 된다.
### 3.1.1 왜 Pod 를 사용하는가? ### 3.1.1 왜 Pod 를 사용하는가?
그냥 하나의 컨테이너 안에서 모든 것을 실행하면 안 되는 것인가? 그냥 하나의 컨테이너 안에서 모든 것을 실행하면 안 되는 것인가?
하나의 프로세스라면 하나의 컨테이너 안에서 실행해도 괜찮지만, 여러 프로세스가 한꺼번에 동작하는 앱의 경우 한 컨테이너 안에서 돌아가게 되면 관리가 어렵다. 하나의 프로세스라면 하나의 컨테이너 안에서 실행해도 괜찮지만, 여러 프로세스가 한꺼번에 동작하는 앱의 경우 한 컨테이너 안에서 돌아가게 되면 관리가 어렵다.
모든 프로세스가 정상적으로 돌아가고 있는지 개발자가 직접 확인해야 하고, 프로세스가 죽으면 직접 다시 시작해줘야 하며, 실제 서비스에서 특정 프로세스가 지나친 병목을 일으키는 경우에는 scale out 을 적용하여 해당 프로세스만 여러 개 실행하여 throughput 을 늘릴 수도 있을 것이다. 모든 프로세스가 정상적으로 돌아가고 있는지 개발자가 직접 확인해야 하고, 프로세스가 죽으면 직접 다시 시작해줘야 하며, 실제 서비스에서 특정 프로세스가 지나친 병목을 일으키는 경우에는 scale out 을 적용하여 해당 프로세스만 여러 개 실행하여 throughput 을 늘릴 수도 있을 것이다.
그러므로 컨테이너를 여러 개 쓰는 것이 보다 바람직하다. 그러므로 컨테이너를 여러 개 쓰는 것이 보다 바람직하다.
### 3.1.2 Pod 에 대한 이해 ### 3.1.2 Pod 에 대한 이해
컨테이너 하나에서 여러 프로세스를 실행하는 것은 바람직하지 않으므로, 컨테이너 여러 개를 하나로 묶어서 관리해줄 상위 레벨의 개념이 필요하다. 그래서 Pod 가 등장하게 된 것이다. 컨테이너 하나에서 여러 프로세스를 실행하는 것은 바람직하지 않으므로, 컨테이너 여러 개를 하나로 묶어서 관리해줄 상위 레벨의 개념이 필요하다. 그래서 Pod 가 등장하게 된 것이다.
#### Pod 내의 partial isolation #### Pod 내의 partial isolation
Pod 를 사용하면 서로 연관된 프로세스를 실행하는 컨테이너들을 함께 실행할 수 있게 되며, 마치 하나의 머신/컨테이너 안에서 실행되는 것처럼 환경을 제공할 수 있게 된다. Pod 를 사용하면 서로 연관된 프로세스를 실행하는 컨테이너들을 함께 실행할 수 있게 되며, 마치 하나의 머신/컨테이너 안에서 실행되는 것처럼 환경을 제공할 수 있게 된다.
물론 각 컨테이너도 서로 어느 정도 독립적으로 동작하지만, 같은 pod 내의 container 는 같은 namespace 를 가지고 있고, 또 쿠버네티스 *volume* 을 사용해서 컨테이너들 끼리 파일을 공유하도록 설정할 수도 있다. 물론 각 컨테이너도 서로 어느 정도 독립적으로 동작하지만, 같은 pod 내의 container 는 같은 namespace 를 가지고 있고, 또 쿠버네티스 *volume* 을 사용해서 컨테이너들 끼리 파일을 공유하도록 설정할 수도 있다.
#### Shared IP and port space #### Shared IP and port space
같은 pod 내의 컨테이너들은 IP 를 공유하므로, 하나의 포트에는 하나의 서비스만 할당되어야 한다. 또 같은 *loopback network interface* 를 사용하므로 localhost 를 사용해 컨테이너들 끼리 통신할 수 있다. 같은 pod 내의 컨테이너들은 IP 를 공유하므로, 하나의 포트에는 하나의 서비스만 할당되어야 한다. 또 같은 *loopback network interface* 를 사용하므로 localhost 를 사용해 컨테이너들 끼리 통신할 수 있다.
#### Flat inter-pod nework #### Flat inter-pod nework
Pod 마다 IP 가 할당되어 있고, 모든 pod 들은 하나의 flat (network topology 말하는 듯), 공유된 네트워크 주소 공간을 사용한다. 그러므로 pod 들 끼리 서로의 IP 주소를 이용해 통신할 수 있으며, NAT(Network Address Translation) gateway 가 사이에 존재하지 않는다. 이는 pod 가 서로 다른 노드에 배치되더라도 상관없다. Pod 마다 IP 가 할당되어 있고, 모든 pod 들은 하나의 flat (network topology 말하는 듯), 공유된 네트워크 주소 공간을 사용한다. 그러므로 pod 들 끼리 서로의 IP 주소를 이용해 통신할 수 있으며, NAT(Network Address Translation) gateway 가 사이에 존재하지 않는다. 이는 pod 가 서로 다른 노드에 배치되더라도 상관없다.
이렇게 동작하기 위해서는 SDN(Software Defined Network) 레이어를 하나 만들어서 사용한다. 이렇게 동작하기 위해서는 SDN(Software Defined Network) 레이어를 하나 만들어서 사용한다.
#### 요약 #### 요약
Pod 는 논리적인 호스트이며, 컨테이너를 사용하지 않는다면 물리적인 호스트나 VM과 다를바 없다. 한 pod 내의 프로세스들은 같은 머신에서 동작하는 프로세스들이며, 차이점은 각 프로세스가 컨테이너 안에서 돌아간다는 점이다. Pod 는 논리적인 호스트이며, 컨테이너를 사용하지 않는다면 물리적인 호스트나 VM과 다를바 없다. 한 pod 내의 프로세스들은 같은 머신에서 동작하는 프로세스들이며, 차이점은 각 프로세스가 컨테이너 안에서 돌아간다는 점이다.
### 3.1.3 Pod 내의 컨테이너 배치 ### 3.1.3 Pod 내의 컨테이너 배치
Pod 는 개별적인 머신이지만 한 머신이 하나의 앱을 실행한다고 이해해야한다. Pod 는 개별적인 머신이지만 한 머신이 하나의 앱을 실행한다고 이해해야한다.
Pod 는 가벼우므로 거의 오버헤드 없이 여러 개를 실행할 수 있으므로, 하나의 pod 에 모든 것을 꾸겨 넣기 보다는 여러 개의 pod 를 사용해서 한 pod 내에는 관련이 깊은 작업만 있또록 관리하는 것이 좋다. Pod 는 가벼우므로 거의 오버헤드 없이 여러 개를 실행할 수 있으므로, 하나의 pod 에 모든 것을 꾸겨 넣기 보다는 여러 개의 pod 를 사용해서 한 pod 내에는 관련이 깊은 작업만 있또록 관리하는 것이 좋다.
#### 하나의 앱을 여러 컨테이너로 분리 #### 하나의 앱을 여러 컨테이너로 분리
백엔드, 프론트엔드가 있는 서비스를 생각해보면, 각각을 하나의 pod 내에 넣지 말라는 법은 없지만 바람직한 방법은 아니다. 백엔드, 프론트엔드가 있는 서비스를 생각해보면, 각각을 하나의 pod 내에 넣지 말라는 법은 없지만 바람직한 방법은 아니다.
만약 쿠버네티스 클러스터에 여러 개의 노드가 있다면, 차라리 두 개의 pod 로 분리해서 CPU 등 하드웨어 자원을 더 효율적으로 사용할 수 있을 것이다. 만약 쿠버네티스 클러스터에 여러 개의 노드가 있다면, 차라리 두 개의 pod 로 분리해서 CPU 등 하드웨어 자원을 더 효율적으로 사용할 수 있을 것이다.
더불어 쿠버네티스에서 scaling 의 단위는 pod 이다. 컨테이너 단위로는 복제할 수 없다. (정말? - 확인 필요) 더불어 쿠버네티스에서 scaling 의 단위는 pod 이다. 컨테이너 단위로는 복제할 수 없다. (정말? - 확인 필요)
(+ 왜 스케일링의 단위가 컨테이너가 아닌 pod 로 설계했을까? 관련이 깊은 작업들은 묶어서 한꺼번에 스케일링 하기 위해? 각각을 복제하고 묶는 것보다는 나을 것 같긴 하다.) (+ 왜 스케일링의 단위가 컨테이너가 아닌 pod 로 설계했을까? 관련이 깊은 작업들은 묶어서 한꺼번에 스케일링 하기 위해? 각각을 복제하고 묶는 것보다는 나을 것 같긴 하다.)
또한 각 애플리케이션 마다 scaling 을 위한 조건이 다를 수 있다. 프론트/백 모두 한 pod 내에 있었다면 스케일링 했을 경우 프론트/백 모두 2개씩 존재하게 된다. 한편 백엔드의 경우 DB가 있다면 scale out 이 어려울 수 있으며, 경우에 따라서 프론트/백 중 어느 하나만 부하가 심해 하나만 scale out 해야하는 상황이 올 수도 있다. 또한 각 애플리케이션 마다 scaling 을 위한 조건이 다를 수 있다. 프론트/백 모두 한 pod 내에 있었다면 스케일링 했을 경우 프론트/백 모두 2개씩 존재하게 된다. 한편 백엔드의 경우 DB가 있다면 scale out 이 어려울 수 있으며, 경우에 따라서 프론트/백 중 어느 하나만 부하가 심해 하나만 scale out 해야하는 상황이 올 수도 있다.
차라리 이들을 분리하여 각각 하나의 pod 에서 실행되도록 하는 것이 바람직한 방법이다. 차라리 이들을 분리하여 각각 하나의 pod 에서 실행되도록 하는 것이 바람직한 방법이다.
#### 어떤 컨테이너들을 한 pod 내에 묶을 것인가 #### 어떤 컨테이너들을 한 pod 내에 묶을 것인가
Pod 내에 여러 컨테이너를 넣는다면 그에 합당한 이유가 있어야 한다. 컨테이너가 서로 긴밀하게 연관되어 있어야 한다. Main 컨테이너가 하나 있고 나머지가 해당 컨테이너를 support 한다던가 (sidecar), 혹은 컨테이너들이 하나의 volume 을 공유해야 하는 상황이라던가. Pod 내에 여러 컨테이너를 넣는다면 그에 합당한 이유가 있어야 한다. 컨테이너가 서로 긴밀하게 연관되어 있어야 한다. Main 컨테이너가 하나 있고 나머지가 해당 컨테이너를 support 한다던가 (sidecar), 혹은 컨테이너들이 하나의 volume 을 공유해야 하는 상황이라던가.
대표적으로 로깅을 담당하는 컨테이너의 경우 (logrotate, 단순 수집, 수집 후 어딘가로 전송) 애플리케이션과 함께 같은 pod 내에 존재하는 것이 나을 것이다. 대표적으로 로깅을 담당하는 컨테이너의 경우 (logrotate, 단순 수집, 수집 후 어딘가로 전송) 애플리케이션과 함께 같은 pod 내에 존재하는 것이 나을 것이다.
한 pod 내에 여러 컨테이너를 묶는다면 아래 3가지 질문을 해보면 된다. 한 pod 내에 여러 컨테이너를 묶는다면 아래 3가지 질문을 해보면 된다.
- 같이 실행 되어야 하는가? 아니면 다른 호스트에서 실행되어도 괜찮은가? - 같이 실행 되어야 하는가? 아니면 다른 호스트에서 실행되어도 괜찮은가?
- 여러 컨테이너가 하나처럼 동작하는가? 아니면 독립적인 컴포넌트인가? - 여러 컨테이너가 하나처럼 동작하는가? 아니면 독립적인 컴포넌트인가?
- 함께 스케일링 되어야 하는가? 아니면 따로 스케일링 되어야 하는가? - 함께 스케일링 되어야 하는가? 아니면 따로 스케일링 되어야 하는가?
기본적으로는 pod 를 분리하는 쪽으로 결정하는 것이 바람직하다. 특별한 이유가 없다면! 기본적으로는 pod 를 분리하는 쪽으로 결정하는 것이 바람직하다. 특별한 이유가 없다면!
## 3.2 YAML/JSON 파일로부터 pod 만들기 ## 3.2 YAML/JSON 파일로부터 pod 만들기
--- ---
보통 pod 를 비롯한 쿠버네티스 리소스를 만들 때는 YAML/JSON 파일을 Kubernetes API 에 POST 해서 만든다. 보통 pod 를 비롯한 쿠버네티스 리소스를 만들 때는 YAML/JSON 파일을 Kubernetes API 에 POST 해서 만든다.
물론 `kubectl run ...` 을 사용할 수 있긴 하지만 모든 옵션을 다 지원하지는 않으며, 매번 명령을 입력하기 보다는 파일로 저장하고 관리하여 버전 관리도 할 수 있게 된다. 물론 `kubectl run ...` 을 사용할 수 있긴 하지만 모든 옵션을 다 지원하지는 않으며, 매번 명령을 입력하기 보다는 파일로 저장하고 관리하여 버전 관리도 할 수 있게 된다.
### 3.2.1 YAML descriptor ### 3.2.1 YAML descriptor
```bash ```bash
$ kubectl get po <POD_NAME> -o yaml $ kubectl get po <POD_NAME> -o yaml
``` ```
- Pod 의 정보를 yaml 형태로 출력하고 싶을 때 사용하는 명령어 - Pod 의 정보를 yaml 형태로 출력하고 싶을 때 사용하는 명령어
- 뒤에 `yaml` 대신에 `json` 을 붙이면 JSON 파일로 출력 결과를 볼 수 있다. - 뒤에 `yaml` 대신에 `json` 을 붙이면 JSON 파일로 출력 결과를 볼 수 있다.
복잡하지만 크게 구성은 다음과 같다. 복잡하지만 크게 구성은 다음과 같다.
- 쿠버네티스 API 버전 - 쿠버네티스 API 버전
- 쿠버네티스 object type - 쿠버네티스 object type
- Pod metadata - Pod metadata
- 이름, namespace, 레이블을 비롯한 기타 정보 - 이름, namespace, 레이블을 비롯한 기타 정보
- Pod specification/contents - Pod specification/contents
- Pod 내에 존재하는 내용으로 컨테이너, 볼륨과 같은 정보 - Pod 내에 존재하는 내용으로 컨테이너, 볼륨과 같은 정보
- Pod status - Pod status
- 현재 실행중인 pod 의 상세 정보, 내부 IP 등 - 현재 실행중인 pod 의 상세 정보, 내부 IP 등
### 3.2.2 Pod 을 위한 YAML descriptor 만들기 ### 3.2.2 Pod 을 위한 YAML descriptor 만들기
대략 다음과 같은 내용으로 구성하면 된다. 대략 다음과 같은 내용으로 구성하면 된다.
```yaml ```yaml
apiVersion: # 쿠버네티스 API 버전 apiVersion: # 쿠버네티스 API 버전
kind: Pod # Pod 임을 명시 kind: Pod # Pod 임을 명시
metadata: metadata:
name: # Pod 이름 name: # Pod 이름
spec: spec:
container: container:
- image: # 컨테이너를 실행할 이미지 - image: # 컨테이너를 실행할 이미지
name: # 컨테이너의 이름 name: # 컨테이너의 이름
ports: # 컨테이너가 listen 할 포트 ports: # 컨테이너가 listen 할 포트
- containerPort: - containerPort:
protocol: TCP protocol: TCP
``` ```
### 3.2.3 `kubectl` 로 pod 생성하기 ### 3.2.3 `kubectl` 로 pod 생성하기
```bash ```bash
$ kubectl create -f <YAML_FILE> $ kubectl create -f <YAML_FILE>
``` ```
(`kubectl apply -f` 가 생각나는데 무슨 차이가 있는지 확인해보기) (`kubectl apply -f` 가 생각나는데 무슨 차이가 있는지 확인해보기)
- Argument 로 넘겨준 파일의 설정으로부터 pod 를 생성한다. - Argument 로 넘겨준 파일의 설정으로부터 pod 를 생성한다.
생성 후 잘 돌아가는지 확인하려면 `kubectl get pods` 로 확인하면 된다. 생성 후 잘 돌아가는지 확인하려면 `kubectl get pods` 로 확인하면 된다.
### 3.2.4 로그 확인하기 ### 3.2.4 로그 확인하기
컨테이너 내의 앱은 파일에 로그를 떨구기보다는 보통은 stdout, stderr 로 로그를 내보낸다. 이렇게 하면 다양한 컨테이너들에서 다양한 애플리케이션을 실행할 때 통일된 방법으로 로그를 확인할 수 있다. 컨테이너 내의 앱은 파일에 로그를 떨구기보다는 보통은 stdout, stderr 로 로그를 내보낸다. 이렇게 하면 다양한 컨테이너들에서 다양한 애플리케이션을 실행할 때 통일된 방법으로 로그를 확인할 수 있다.
```bash ```bash
$ docker logs <CONTAINER_ID> $ docker logs <CONTAINER_ID>
``` ```
- 컨테이너 내의 로그 (stdout/stderr)를 파일로 redirection 해준다. - 컨테이너 내의 로그 (stdout/stderr)를 파일로 redirection 해준다.
- 하지만 `docker` 명령어를 실행하는 것이므로 pod 내에 ssh 로 접근해서 해야한다. - 하지만 `docker` 명령어를 실행하는 것이므로 pod 내에 ssh 로 접근해서 해야한다.
쿠버네티스에서는 더 간편한 방법을 제공해준다. 쿠버네티스에서는 더 간편한 방법을 제공해준다.
```bash ```bash
$ kubectl logs <POD_NAME> $ kubectl logs <POD_NAME>
``` ```
- 해당 pod 의 로그를 확인할 수 있게 된다. - 해당 pod 의 로그를 확인할 수 있게 된다.
- Pod 내의 특정 컨테이너 로그를 확인하려면 `-c <CONTAINER_NAME>` 을 붙여주면 된다. - Pod 내의 특정 컨테이너 로그를 확인하려면 `-c <CONTAINER_NAME>` 을 붙여주면 된다.
- 컨테이너 로그는 10MB 마다, 매일 rotation 된다. - 컨테이너 로그는 10MB 마다, 매일 rotation 된다.
물론 컨테이너가 삭제되면 로그가 함께 삭제되므로, 클러스터 전체의 로그를 수집하는 부분이 따로 필요하다. (centralized logging) 이는 나중에 17장에서 다룬다. 물론 컨테이너가 삭제되면 로그가 함께 삭제되므로, 클러스터 전체의 로그를 수집하는 부분이 따로 필요하다. (centralized logging) 이는 나중에 17장에서 다룬다.
### 3.2.5 Pod 에 요청 보내기 ### 3.2.5 Pod 에 요청 보내기
2장에서 한 것처럼 `kubectl expose` 를 사용하는 방법도 있지만 Service object 에 대해서는 나중에 알아보기로 하고, 포트포워딩으로 하는 방법을 소개한다. 2장에서 한 것처럼 `kubectl expose` 를 사용하는 방법도 있지만 Service object 에 대해서는 나중에 알아보기로 하고, 포트포워딩으로 하는 방법을 소개한다.
```bash ```bash
$ kubectl port-forward <POD_NAME> <LOCAL_PORT>:<POD_PORT> $ kubectl port-forward <POD_NAME> <LOCAL_PORT>:<POD_PORT>
``` ```
- 로컬의 포트를 pod 의 포트로 포워딩해준다. - 로컬의 포트를 pod 의 포트로 포워딩해준다.
## 3.3 레이블을 이용한 pod 구성 ## 3.3 레이블을 이용한 pod 구성
--- ---
Pod 의 개수가 많아질수록 관리가 힘들어지고, 한 서비스에 pod 가 여러 개 존재하게 되므로 (복제본) pod 를 묶어서 관리할 방법이 필요하다. 쿠버네티스는 pod 뿐만 아니라 다른 object 들도 **레이블**을 이용해서 관리한다. Pod 의 개수가 많아질수록 관리가 힘들어지고, 한 서비스에 pod 가 여러 개 존재하게 되므로 (복제본) pod 를 묶어서 관리할 방법이 필요하다. 쿠버네티스는 pod 뿐만 아니라 다른 object 들도 **레이블**을 이용해서 관리한다.
### 3.3.1 레이블 ### 3.3.1 레이블
레이블은 key-value pair 로, 쿠버네티스 리소스에 붙일 수 있으며, label selector 를 이용해 리소스를 필터링할 때 사용된다. 레이블은 key-value pair 로, 쿠버네티스 리소스에 붙일 수 있으며, label selector 를 이용해 리소스를 필터링할 때 사용된다.
한 리소스에 레이블은 여러 개 붙을 수 있으며 key 가 유일하기만 하면 된다. 한 리소스에 레이블은 여러 개 붙을 수 있으며 key 가 유일하기만 하면 된다.
보통 리소스를 생성할 때 레이블을 붙이지만, 리소스를 다시 생성할 필요 없이 레이블을 붙일 수도 있다. 보통 리소스를 생성할 때 레이블을 붙이지만, 리소스를 다시 생성할 필요 없이 레이블을 붙일 수도 있다.
### 3.3.2 Pod 생성 시 레이블 붙이기 ### 3.3.2 Pod 생성 시 레이블 붙이기
당연히 yaml 파일에 레이블을 추가해주면 된다. `metadata` 아래에 추가하면 된다. 당연히 yaml 파일에 레이블을 추가해주면 된다. `metadata` 아래에 추가하면 된다.
```yaml ```yaml
metadata: metadata:
name: ... name: ...
labels: labels:
key_1: value_1 key_1: value_1
key_2: value_2 key_2: value_2
... ...
``` ```
이렇게 하고 pod 를 실행한 뒤, `kubectl get pods` 를 해보면 레이블이 보이지 않는다. 이렇게 하고 pod 를 실행한 뒤, `kubectl get pods` 를 해보면 레이블이 보이지 않는다.
`--show-labels` 옵션을 설정해야 레이블이 같이 출력된다. 대신 모든 레이블이 출력되므로 보기 불편할 수 있다. `-L <LABEL_KEYS>` 옵션을 주면 해당 key 에 대한 레이블만 보여준다. Key 가 여러 개인 경우 `,` 로 구분한다. `--show-labels` 옵션을 설정해야 레이블이 같이 출력된다. 대신 모든 레이블이 출력되므로 보기 불편할 수 있다. `-L <LABEL_KEYS>` 옵션을 주면 해당 key 에 대한 레이블만 보여준다. Key 가 여러 개인 경우 `,` 로 구분한다.
### 3.3.3 레이블 수정하기 ### 3.3.3 레이블 수정하기
```bash ```bash
$ kubectl label po <POD_NAME> <LABEL_KEY>=<LABEL_VALUE> $ kubectl label po <POD_NAME> <LABEL_KEY>=<LABEL_VALUE>
``` ```
- Pod 에 새롭게 레이블을 붙인다. - Pod 에 새롭게 레이블을 붙인다.
- 해당 key 가 존재하지 않아야 한다. 만약 덮어쓰고 싶다면 `--overwrite` 를 붙여야 한다. - 해당 key 가 존재하지 않아야 한다. 만약 덮어쓰고 싶다면 `--overwrite` 를 붙여야 한다.
## 3.4 Label selector 를 사용하여 pod 목록 출력하기 ## 3.4 Label selector 를 사용하여 pod 목록 출력하기
--- ---
레이블에 따라 검색하는 기능을 제공한다. 레이블에 따라 검색하는 기능을 제공한다.
- 특정 key 를 갖거나 갖지 않는지 - 특정 key 를 갖거나 갖지 않는지
- 특정 key-value 를 갖는지 - 특정 key-value 를 갖는지
- 특정 key 를 갖지만 해당 value 가 아닌 값을 갖는지 - 특정 key 를 갖지만 해당 value 가 아닌 값을 갖는지
### 3.4.1 Listing pods using a label selector ### 3.4.1 Listing pods using a label selector
```bash ```bash
$ kubectl get po -l [CONDITION] $ kubectl get po -l [CONDITION]
``` ```
- `-l` 옵션을 이용해서 label selector 를 사용할 수 있다. - `-l` 옵션을 이용해서 label selector 를 사용할 수 있다.
`CONDITION` 내에는 다음과 같은 구문을 넣을 수 있다. `CONDITION` 내에는 다음과 같은 구문을 넣을 수 있다.
- `<KEY>=<VALUE>`: 해당 key-value pair 를 갖는 pod 출력 - `<KEY>=<VALUE>`: 해당 key-value pair 를 갖는 pod 출력
- `<KEY>`: 해당 key 를 갖는 pod 출력 - `<KEY>`: 해당 key 를 갖는 pod 출력
- `!<KEY>`: 해당 key 를 갖지 않는 pod 출력 - `!<KEY>`: 해당 key 를 갖지 않는 pod 출력
- `'!<KEY>'` 와 같이 `'...'` 로 감싸주어야 shell 이 `!` 를 계산하지 않는다. - `'!<KEY>'` 와 같이 `'...'` 로 감싸주어야 shell 이 `!` 를 계산하지 않는다.
- `<KEY>!=<VALUE>`: 해당 key 를 갖지만 value 가 아닌 pod 출력 - `<KEY>!=<VALUE>`: 해당 key 를 갖지만 value 가 아닌 pod 출력
- `<KEY> in (<VALUE_1>, <VALUE_2>, ...)`: key 의 값이 value 목록에 포함되는 pod 를 출력 - `<KEY> in (<VALUE_1>, <VALUE_2>, ...)`: key 의 값이 value 목록에 포함되는 pod 를 출력
- `<KEY> notin (<VALUE_1>, <VALUE_2>, ...)`: key 의 값이 value 목록에 포함되지 않는 pod 를 출력 - `<KEY> notin (<VALUE_1>, <VALUE_2>, ...)`: key 의 값이 value 목록에 포함되지 않는 pod 를 출력
### 3.4.2 Label selector 에서 여러 조건을 이용하기 ### 3.4.2 Label selector 에서 여러 조건을 이용하기
조건을 여러 개 적용하고 싶다면 `,` 로 구분하면 된다. 조건을 여러 개 적용하고 싶다면 `,` 로 구분하면 된다.
이 section 에서는 pod 목록을 가져오는 용도로 레이블을 사용했지만, 여러 개의 pod 를 한꺼번에 삭제할 때도 사용할 수 있는 등 특정 명령을 pod 의 부분집합에 적용할 수 있게 된다. 이 section 에서는 pod 목록을 가져오는 용도로 레이블을 사용했지만, 여러 개의 pod 를 한꺼번에 삭제할 때도 사용할 수 있는 등 특정 명령을 pod 의 부분집합에 적용할 수 있게 된다.
## 3.5 Pod 스케쥴링 제한 ## 3.5 Pod 스케쥴링 제한
--- ---
일반적으로는 worker node 들에 pod 들이 적당히 배치되어 돌아간다. 그리고 밖에서 볼 때는 어떤 pod 가 어느 노드에 있는지는 보통 상관 없다. 하지만 pod 가 특정 하드웨어가 필요한 경우에는 pod 가 어느 노드로 스케쥴링 되어야 하는지 제한할 필요가 있다. (GPU가 필요하다던가...) 일반적으로는 worker node 들에 pod 들이 적당히 배치되어 돌아간다. 그리고 밖에서 볼 때는 어떤 pod 가 어느 노드에 있는지는 보통 상관 없다. 하지만 pod 가 특정 하드웨어가 필요한 경우에는 pod 가 어느 노드로 스케쥴링 되어야 하는지 제한할 필요가 있다. (GPU가 필요하다던가...)
한편 특정 노드에 이 pod 를 스케쥴링 해주라고 요청하게 되면 쿠버네티스를 사용할 이유가 없어진다. 앱과 인프라를 분리하지 못하게 된다. 그러므로 특정 노드에 스케쥴링 해야한다고 명시하지는 않고, 이 pod 를 실행할 노드가 갖춰야 할 요구사항을 적는다. 이를 위해 노드 레이블과 노드 label selector 를 사용한다. 한편 특정 노드에 이 pod 를 스케쥴링 해주라고 요청하게 되면 쿠버네티스를 사용할 이유가 없어진다. 앱과 인프라를 분리하지 못하게 된다. 그러므로 특정 노드에 스케쥴링 해야한다고 명시하지는 않고, 이 pod 를 실행할 노드가 갖춰야 할 요구사항을 적는다. 이를 위해 노드 레이블과 노드 label selector 를 사용한다.
### 3.5.1 Categorizing worker nodes ### 3.5.1 Categorizing worker nodes
레이블은 다양한 리소스에 붙일 수 있다. 명령어도 거의 비슷하다. 레이블은 다양한 리소스에 붙일 수 있다. 명령어도 거의 비슷하다.
```bash ```bash
$ kubectl label node <NODE_NAME> <KEY>=<VALUE> $ kubectl label node <NODE_NAME> <KEY>=<VALUE>
``` ```
### 3.5.2 특정 노드들로 pod 스케쥴링 ### 3.5.2 특정 노드들로 pod 스케쥴링
Pod 의 yaml 파일을 수정하면 된다. Pod 의 yaml 파일을 수정하면 된다.
노드에 `gpu=true` 라는 레이블이 있다고 하면, GPU 가 필요한 pod 의 yaml 파일에 다음을 추가한다. 노드에 `gpu=true` 라는 레이블이 있다고 하면, GPU 가 필요한 pod 의 yaml 파일에 다음을 추가한다.
```yaml ```yaml
spec: spec:
nodeSelector: nodeSelector:
gpu: "true" gpu: "true"
``` ```
그러면 이 레이블을 가진 노드에만 pod 가 스케쥴링 되게 된다. 그러면 이 레이블을 가진 노드에만 pod 가 스케쥴링 되게 된다.
### 3.5.3 특정 노드 하나로 pod 스케쥴링 ### 3.5.3 특정 노드 하나로 pod 스케쥴링
가능한데 하지마! 가능한데 하지마!
각 노드는 기본적으로 레이블 `kubernetes.io/hostname=<ACTUAL_HOSTNAME>` 을 가지므로 이를 이용하면 특정 노드에 pod 를 스케쥴링 할 수 있다. 각 노드는 기본적으로 레이블 `kubernetes.io/hostname=<ACTUAL_HOSTNAME>` 을 가지므로 이를 이용하면 특정 노드에 pod 를 스케쥴링 할 수 있다.
하지만 이 노드가 죽어있으면 pod 가 실행되지 못한다. 하지만 이 노드가 죽어있으면 pod 가 실행되지 못한다.
개별적인 하나의 노드가 아니라 node label selector 를 이용해 특정 성질을 가진 노드의 집합을 고려하는 것이 쿠버네티스의 사고방식이다. 개별적인 하나의 노드가 아니라 node label selector 를 이용해 특정 성질을 가진 노드의 집합을 고려하는 것이 쿠버네티스의 사고방식이다.
## 3.6 Annotating Pods ## 3.6 Annotating Pods
--- ---
쿠버네티스 리소스들은 레이블 뿐만 아니라 어노테이션도 가질 수 있는데, 얘도 key-value pair 이다. 레이블과 비슷하지만 selector 가 따로 존재하지는 않아서 이를 이용해 리소스를 분류할 수는 없다. 쿠버네티스 리소스들은 레이블 뿐만 아니라 어노테이션도 가질 수 있는데, 얘도 key-value pair 이다. 레이블과 비슷하지만 selector 가 따로 존재하지는 않아서 이를 이용해 리소스를 분류할 수는 없다.
(다른 툴들이 annotation 을 사용한다고 하는데 어떤 툴들이 쓰는지 알아보기) (다른 툴들이 annotation 을 사용한다고 하는데 어떤 툴들이 쓰는지 알아보기)
### 3.6.1 Object 의 어노테이션 확인하기 ### 3.6.1 Object 의 어노테이션 확인하기
Object 의 yaml 파일을 확인해 보면 `metadata` 아래에 있다. Object 의 yaml 파일을 확인해 보면 `metadata` 아래에 있다.
### 3.6.2 어노테이션 추가 및 수정 ### 3.6.2 어노테이션 추가 및 수정
```bash ```bash
$ kubectl annotate pod <POD_NAME> <KEY>=<VALUE> $ kubectl annotate pod <POD_NAME> <KEY>=<VALUE>
``` ```
어노테이션 추가시 key 충돌을 막기 위해 unique prefix 를 사용하는 것이 좋다. 어노테이션 추가시 key 충돌을 막기 위해 unique prefix 를 사용하는 것이 좋다.
`kubectl describe pod <POD_NAME>` 으로 추가된 어노테이션을 확인할 수 있다. `kubectl describe pod <POD_NAME>` 으로 추가된 어노테이션을 확인할 수 있다.
## 3.7 Namespace 를 이용한 리소스 그룹화 ## 3.7 Namespace 를 이용한 리소스 그룹화
--- ---
`kubectl get pods` 를 했을 때, 매번 레이블을 이용해서 필터링하지 않으면 모든 pod 가 다 목록에 출력된다. 또한 레이블은 한 집합 내에서 부분집합으로 구분하기 위해서 사용했다. 부분집합들은 서로 겹치기도 한다. `kubectl get pods` 를 했을 때, 매번 레이블을 이용해서 필터링하지 않으면 모든 pod 가 다 목록에 출력된다. 또한 레이블은 한 집합 내에서 부분집합으로 구분하기 위해서 사용했다. 부분집합들은 서로 겹치기도 한다.
만약 아예 다른 집합을 만들어 겹치지 않도록 분리하고 싶은 경우도 있을 것이다. (다른 이유가 더 있지만 - 뭐지?) 쿠버네티스에서는 namespace 를 이용해서 object 를 그룹화 할 수 있다. 만약 아예 다른 집합을 만들어 겹치지 않도록 분리하고 싶은 경우도 있을 것이다. (다른 이유가 더 있지만 - 뭐지?) 쿠버네티스에서는 namespace 를 이용해서 object 를 그룹화 할 수 있다.
linux namespace 와는 다르다! linux namespace 와는 다르다!
### 3.7.1 Namespace 의 필요성 ### 3.7.1 Namespace 의 필요성
Namespace 를 사용하면 많은 컴포넌트를 가진 복잡한 시스템을 작고 분리된 그룹으로 나누어 관리할 수 있다. 컴퓨팅 리소스를 개발, 배포, QA 환경 용으로 각각 분리할 수 있게 되며, namespace 안에서만 리소스 이름이 유일하면 된다. Namespace 를 사용하면 많은 컴포넌트를 가진 복잡한 시스템을 작고 분리된 그룹으로 나누어 관리할 수 있다. 컴퓨팅 리소스를 개발, 배포, QA 환경 용으로 각각 분리할 수 있게 되며, namespace 안에서만 리소스 이름이 유일하면 된다.
만약 여러 사용자가 한 클러스터에 접근하여 작업하는 경우 각 사용자마다 독립적으로 리소스를 관리하고 싶다면 namespace 를 사용하여 사용자별로 분리하면 될 것이다. 의도치 않게 다른 사용자들의 리소스를 건드리거나, 이름이 충돌하는 일이 생기지 않게 된다. 만약 여러 사용자가 한 클러스터에 접근하여 작업하는 경우 각 사용자마다 독립적으로 리소스를 관리하고 싶다면 namespace 를 사용하여 사용자별로 분리하면 될 것이다. 의도치 않게 다른 사용자들의 리소스를 건드리거나, 이름이 충돌하는 일이 생기지 않게 된다.
더불어 namespace 에 따라 접근 제한을 할 수 있는데 12~14장에서 더 알아본다. 더불어 namespace 에 따라 접근 제한을 할 수 있는데 12~14장에서 더 알아본다.
거의 대부분의 리소스들이 namespace 로 관리할 수 있지만, 몇 개는 그렇지 않으며 대표적으로 노드의 경우 namespace 에 구애받지 않는다. 거의 대부분의 리소스들이 namespace 로 관리할 수 있지만, 몇 개는 그렇지 않으며 대표적으로 노드의 경우 namespace 에 구애받지 않는다.
(아마 다른 cluster 레벨의 리소스도 구애받지 않을 것 같다) (아마 다른 cluster 레벨의 리소스도 구애받지 않을 것 같다)
### 3.7.2 Namespace 확인 ### 3.7.2 Namespace 확인
```bash ```bash
$ kubectl get namespace $ kubectl get namespace
$ kubectl get ns # short $ kubectl get ns # short
``` ```
처음에는 `default` namespace 에서 작업하게 된다. `kubectl get` 명령을 할 때에도 namespace 를 지정하지 않아서 `default` namespace 에 대해 명령을 실행하게 된다. 처음에는 `default` namespace 에서 작업하게 된다. `kubectl get` 명령을 할 때에도 namespace 를 지정하지 않아서 `default` namespace 에 대해 명령을 실행하게 된다.
`kubectl get` 명령에 `-n` (`--namespace`) 옵션을 주어 namespace 를 제한할 수 있다. `kubectl get` 명령에 `-n` (`--namespace`) 옵션을 주어 namespace 를 제한할 수 있다.
### 3.7.3 Namespace 만들기 ### 3.7.3 Namespace 만들기
Namespace 또한 쿠버네티스 리소스이므로, yaml 파일을 이용해 만들 수 있다. Namespace 또한 쿠버네티스 리소스이므로, yaml 파일을 이용해 만들 수 있다.
```yaml ```yaml
apiVersion: ... apiVersion: ...
kind: Namespace kind: Namespace
metadata: metadata:
name: ... name: ...
``` ```
파일을 만드는 것은 번거로울 수 있으므로 명령어를 사용하는 방법도 있다. 파일을 만드는 것은 번거로울 수 있으므로 명령어를 사용하는 방법도 있다.
```bash ```bash
$ kubectl create namespace <NAMESPACE_NAME> $ kubectl create namespace <NAMESPACE_NAME>
``` ```
### 3.7.4 다른 namespace 의 object 관리 ### 3.7.4 다른 namespace 의 object 관리
특정 namespace 에 리소스를 만들고 싶다면, yaml 파일에서 `metadata` 섹션 아래에 `namespace: <NAMESPACE_NAME>` 엔트리를 넣어주면 된다. 다만 같은 리소스를 여러 namespace 에 만들고 싶을 수 있으므로 다음과 같이 할 수도 있다. 특정 namespace 에 리소스를 만들고 싶다면, yaml 파일에서 `metadata` 섹션 아래에 `namespace: <NAMESPACE_NAME>` 엔트리를 넣어주면 된다. 다만 같은 리소스를 여러 namespace 에 만들고 싶을 수 있으므로 다음과 같이 할 수도 있다.
```bash ```bash
$ kubectl create -f <YAML_FILE> -n <NAMESPACE_NAME> $ kubectl create -f <YAML_FILE> -n <NAMESPACE_NAME>
``` ```
- `-n` 옵션으로 namespace 를 넘겨주면 된다. - `-n` 옵션으로 namespace 를 넘겨주면 된다.
즉 `-n` 옵션을 이용하면 다른 namespace 에 있는 리소스도 관리할 수 있게 되며, `-n` 옵션이 없으면 현재 설정된 kubectl context 의 namespace 에서 동작하게 된다. `-n` 옵션을 이용하면 다른 namespace 에 있는 리소스도 관리할 수 있게 되며, `-n` 옵션이 없으면 현재 설정된 kubectl context 의 namespace 에서 동작하게 된다.
### 3.7.5 Namespace 가 제공하는 환경 분리 이해하기 ### 3.7.5 Namespace 가 제공하는 환경 분리 이해하기
Namespace 를 사용하면 object 들을 분리하여 그룹화 한 후, 특정 그룹에 대해서만 작업을 할 수 있지만, object 자체를 격리하는 것은 아니다. Namespace 를 사용하면 object 들을 분리하여 그룹화 한 후, 특정 그룹에 대해서만 작업을 할 수 있지만, object 자체를 격리하는 것은 아니다.
예를 들어 두 pod 가 다른 namespace 에 있다고 해서 반드시 통신이 불가능한 것은 아니다. 예를 들어 두 pod 가 다른 namespace 에 있다고 해서 반드시 통신이 불가능한 것은 아니다.
## 3.8 Pod 중지 및 삭제 ## 3.8 Pod 중지 및 삭제
--- ---
### 3.8.1 이름으로 삭제 ### 3.8.1 이름으로 삭제
```bash ```bash
$ kubectl delete pod <POD_NAME> $ kubectl delete pod <POD_NAME>
``` ```
### 3.8.2 Label selector 로 삭제 ### 3.8.2 Label selector 로 삭제
```bash ```bash
$ kubectl delete pod -l <KEY>=<VALUE> $ kubectl delete pod -l <KEY>=<VALUE>
``` ```
(왠지 label selector 에서 제공하는 필터링 기능을 다 사용할 수 있을 것 같다.) (왠지 label selector 에서 제공하는 필터링 기능을 다 사용할 수 있을 것 같다.)
### 3.8.3 Pod 이 속한 namespace 삭제 ### 3.8.3 Pod 이 속한 namespace 삭제
```bash ```bash
$ kubectl delete ns <NAMESPACE_NAME> $ kubectl delete ns <NAMESPACE_NAME>
``` ```
### 3.8.4 Namespace 는 남기고 pod 만 삭제 ### 3.8.4 Namespace 는 남기고 pod 만 삭제
```bash ```bash
$ kubectl delete pod --all $ kubectl delete pod --all
``` ```
### 3.8.5 거의 모든 리소스 삭제 ### 3.8.5 거의 모든 리소스 삭제
```bash ```bash
$ kubectl delete all --all $ kubectl delete all --all
``` ```
몇몇 object 들은 사용자가 직접 삭제해줘야 해서 `all` 로 지워도 지워지지 않는다. 몇몇 object 들은 사용자가 직접 삭제해줘야 해서 `all` 로 지워도 지워지지 않는다.
(안 쓰는게 좋을 것 같다.) (안 쓰는게 좋을 것 같다.)
--- ---
## Discussion & Additional Topics ## Discussion & Additional Topics
### `kubectl apply` 와 `kubectl create` 의 차이점 ### `kubectl apply` 와 `kubectl create` 의 차이점
- Imperative vs Declarative - Imperative vs Declarative
- https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/ - https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/
- https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create - https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create
### Kubernetes object 와 Kubernetes resource 의 차이점 ### Kubernetes object 와 Kubernetes resource 의 차이점
- https://stackoverflow.com/a/59952807/7788730 - https://stackoverflow.com/a/59952807/7788730
### Namespace 의 올바른 사용법 ### Namespace 의 올바른 사용법
### JSON vs YAML ### JSON vs YAML
- https://stackoverflow.com/questions/1726802/what-is-the-difference-between-yaml-and-json - https://stackoverflow.com/questions/1726802/what-is-the-difference-between-yaml-and-json
### Label 과 namespace 의 차이 ### Label 과 namespace 의 차이
- Label selector 를 이용하면 namespace 처럼 분리가 가능 - Label selector 를 이용하면 namespace 처럼 분리가 가능
- 하지만 namespace 는 label 보다 상위 개념 - 하지만 namespace 는 label 보다 상위 개념
- Namespace 를 이용하면 resource quota 를 이용해서 하드웨어 리소스 분배가 가능 - Namespace 를 이용하면 resource quota 를 이용해서 하드웨어 리소스 분배가 가능
- Namespace 를 이용하면 접근 제한 가능 - Namespace 를 이용하면 접근 제한 가능
### Annotation 의 올바른 사용법 ### Annotation 의 올바른 사용법
### Linux 에 존재하는 namespace 들의 종류 ### Linux 에 존재하는 namespace 들의 종류
- UTS namespace 란? - UTS namespace 란?
### Pod 에 각각 IP 주소가 부여되는 원리 ### Pod 에 각각 IP 주소가 부여되는 원리
- https://ronaknathani.com/blog/2020/08/how-a-kubernetes-pod-gets-an-ip-address/ - https://ronaknathani.com/blog/2020/08/how-a-kubernetes-pod-gets-an-ip-address/
### MSA vs Microservice 간 통신으로 인한 오버헤드 ### MSA vs Microservice 간 통신으로 인한 오버헤드
- MSA 를 사용하면 - MSA 를 사용하면
- 책임이 분배되어 프로젝트 관리가 용이하고 - 책임이 분배되어 프로젝트 관리가 용이하고
- 개발하기 편하고 확장이 쉬워짐 (microservice 별로 확장) - 개발하기 편하고 확장이 쉬워짐 (microservice 별로 확장)
- 다만 통신으로 인한 오버헤드는 증가하는 것이 맞을 것이다. - 다만 통신으로 인한 오버헤드는 증가하는 것이 맞을 것이다.

View File

@@ -1,458 +1,458 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops] tags: [kubernetes, sre, devops]
title: "04. Replication and Other Controllers: Deploying Managed Pods" title: "04. Replication and Other Controllers: Deploying Managed Pods"
date: "2021-03-21" date: "2021-03-21"
github_title: "2021-03-21-04-replication-and-other-controllers" github_title: "2021-03-21-04-replication-and-controllers"
image: image:
path: /assets/img/posts/k8s-04.jpeg path: /assets/img/posts/k8s-04.jpeg
--- ---
![k8s-04.jpeg](../../../assets/img/posts/k8s-04.jpeg) _ReplicationController recreating pods. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-4)_ ![k8s-04.jpeg](../../../assets/img/posts/k8s-04.jpeg) _ReplicationController recreating pods. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-4)_
3장에서는 pod 를 직접 관리하는 방법에 대해 살펴봤다. 하지만 실무에서는 pod 의 관리가 자동으로 되길 원한다. 이를 위해 ReplicationController 나 Deployment 를 사용한다. 3장에서는 pod 를 직접 관리하는 방법에 대해 살펴봤다. 하지만 실무에서는 pod 의 관리가 자동으로 되길 원한다. 이를 위해 ReplicationController 나 Deployment 를 사용한다.
## 4.1 Keeping pods healthy ## 4.1 Keeping pods healthy
--- ---
Pod 내의 컨테이너가 (오류로 인해) 죽으면, Kubelet 이 자동으로 해당 컨테이너를 재시작한다. 하지만 컨테이너의 프로세스가 종료되지 않았는데 애플리케이션이 동작하지 않는 경우가 있고, (JVM OOM 에러) 애플리케이션이 deadlock 이나 무한 루프에 빠져서 동작하지 않는 경우가 생길 수도 있다. 이런 경우에도 컨테이너가 자동으로 재시작되게 해야한다. 물론 앱이 자체적으로 에러를 감지해서 프로세스를 종료할 수도 있겠지만, 내부적으로 에러를 감지하기 보다는 **컨테이너 밖에서** 컨테이너의 상태를 확인해야 한다. Pod 내의 컨테이너가 (오류로 인해) 죽으면, Kubelet 이 자동으로 해당 컨테이너를 재시작한다. 하지만 컨테이너의 프로세스가 종료되지 않았는데 애플리케이션이 동작하지 않는 경우가 있고, (JVM OOM 에러) 애플리케이션이 deadlock 이나 무한 루프에 빠져서 동작하지 않는 경우가 생길 수도 있다. 이런 경우에도 컨테이너가 자동으로 재시작되게 해야한다. 물론 앱이 자체적으로 에러를 감지해서 프로세스를 종료할 수도 있겠지만, 내부적으로 에러를 감지하기 보다는 **컨테이너 밖에서** 컨테이너의 상태를 확인해야 한다.
(앱이 자체적으로 에러를 감지해서 프로세스를 죽인다면, 에러를 감지하는 로직을 추가로 구현해야 하며, 이는 쿠버네티스에 올린 앱 종류 별로 달라진다. 앱 자체에 의존적인 로직을 구현하고 사용하는 것보다 컨테이너의 밖에서 정해진 방식으로 에러를 감지하는 편이 나을 것 같다.) (앱이 자체적으로 에러를 감지해서 프로세스를 죽인다면, 에러를 감지하는 로직을 추가로 구현해야 하며, 이는 쿠버네티스에 올린 앱 종류 별로 달라진다. 앱 자체에 의존적인 로직을 구현하고 사용하는 것보다 컨테이너의 밖에서 정해진 방식으로 에러를 감지하는 편이 나을 것 같다.)
### 4.1.1 Liveness probes ### 4.1.1 Liveness probes
쿠버네티스는 *liveness probe* 를 이용해서 컨테이너가 동작 중인지 확인한다. Pod 설정에서 컨테이너 별로 이를 지정할 수 있으며, 지정하면 쿠버네티스가 주기적으로 컨테이너의 상태를 확인하게 된다. 쿠버네티스는 *liveness probe* 를 이용해서 컨테이너가 동작 중인지 확인한다. Pod 설정에서 컨테이너 별로 이를 지정할 수 있으며, 지정하면 쿠버네티스가 주기적으로 컨테이너의 상태를 확인하게 된다.
#### 컨테이너 상태 확인하는 3가지 방법 #### 컨테이너 상태 확인하는 3가지 방법
- **HTTP GET probe**: 컨테이너 IP 주소에 GET 요청 보내보기 - **HTTP GET probe**: 컨테이너 IP 주소에 GET 요청 보내보기
- 응답 코드가 2xx, 3xx 이면 OK - 응답 코드가 2xx, 3xx 이면 OK
- 이외의 에러 코드나 응답이 없으면 실패로 간주하고 컨테이너 재시작 - 이외의 에러 코드나 응답이 없으면 실패로 간주하고 컨테이너 재시작
- **TCP socket probe**: 컨테이너의 정해진 포트에 TCP 연결을 시도해서 실패하면 컨테이너 재시작 - **TCP socket probe**: 컨테이너의 정해진 포트에 TCP 연결을 시도해서 실패하면 컨테이너 재시작
- **Exec probe**: 컨테이너 안에서 임의의 명령을 실행하고 exit code 가 0 이 아니면 컨테이너 재시작 - **Exec probe**: 컨테이너 안에서 임의의 명령을 실행하고 exit code 가 0 이 아니면 컨테이너 재시작
### 4.1.2 HTTP-based liveness probe 만들기 ### 4.1.2 HTTP-based liveness probe 만들기
```yaml ```yaml
spec: spec:
containers: containers:
- image: luksa/kubia-unhealthy - image: luksa/kubia-unhealthy
name: sung name: sung
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /
port: 8080 port: 8080
``` ```
`livenessProbe` 에 설정해주면 된다. `httpGet`, `tcpSocket`, `exec` 으로 그 종류를 설정할 수 있다. `livenessProbe` 에 설정해주면 된다. `httpGet`, `tcpSocket`, `exec` 으로 그 종류를 설정할 수 있다.
### 4.1.3 Seeing a liveness probe in action ### 4.1.3 Seeing a liveness probe in action
`kubectl get pods <POD_NAME>` 을 한 뒤 `RESTARTS` column 을 보면 재시작 된 횟수를 알 수 있다. `kubectl get pods <POD_NAME>` 을 한 뒤 `RESTARTS` column 을 보면 재시작 된 횟수를 알 수 있다.
재시작 사유는 `kubectl describe` 를 이용해 확인할 수 있다. `Last State` 를 보면 된다. 재시작 사유는 `kubectl describe` 를 이용해 확인할 수 있다. `Last State` 를 보면 된다.
컨테이너가 종료될 때는 완전히 새로운 컨테이너가 생성된다. 같은 컨테이너가 재시작 되는 것이 아니다. 컨테이너가 종료될 때는 완전히 새로운 컨테이너가 생성된다. 같은 컨테이너가 재시작 되는 것이 아니다.
(그러면 사실 `restart` 라는 단어가 조금 애매해지는데... 사실은 다른 컨테이너지만 겉에서 보기에는 같다 정도로 이해하면 될 것 같다.) (그러면 사실 `restart` 라는 단어가 조금 애매해지는데... 사실은 다른 컨테이너지만 겉에서 보기에는 같다 정도로 이해하면 될 것 같다.)
### 4.1.4 Liveness probe 추가 설정 ### 4.1.4 Liveness probe 추가 설정
#### Liveness probe 추가 옵션 #### Liveness probe 추가 옵션
- `initialDelaySeconds`: 컨테이너가 시작된 직후 첫 liveness 를 체크를 얼마나 미룰 것인지 - `initialDelaySeconds`: 컨테이너가 시작된 직후 첫 liveness 를 체크를 얼마나 미룰 것인지
- 기본값 0s - 기본값 0s
- `timeoutSeconds`: liveness probe 가 실행된 후 몇 초만에 응답이 와야 하는지 - `timeoutSeconds`: liveness probe 가 실행된 후 몇 초만에 응답이 와야 하는지
- 기본값 1s - 기본값 1s
- `periodSeconds`: liveness probe 를 실행할 주기 - `periodSeconds`: liveness probe 를 실행할 주기
- 기본값 10s - 기본값 10s
- `successThreshold`: 컨테이너가 정상적으로 동작한다고 판단할 연속된 liveness probe success 개수 - `successThreshold`: 컨테이너가 정상적으로 동작한다고 판단할 연속된 liveness probe success 개수
- 기본값 1 - 기본값 1
- `failureThreshold`: 컨테이너가 동작하지 않고 있다고 판단할 연속된 liveness probe failure 개수 - `failureThreshold`: 컨테이너가 동작하지 않고 있다고 판단할 연속된 liveness probe failure 개수
- 기본값 3 - 기본값 3
앱이 시작되는 시간이 존재하므로 `initialDelaySeconds` 를 잘 설정해야 한다. 무거운 애플리케이션의 경우 실행되는데 오래 걸릴 수도 있다. 앱이 시작되는 시간이 존재하므로 `initialDelaySeconds` 를 잘 설정해야 한다. 무거운 애플리케이션의 경우 실행되는데 오래 걸릴 수도 있다.
#### Exit code #### Exit code
보통 `128 + x` 로 계산된다. 여기서 `x` 는 프로세스를 죽인 signal 의 번호이다. 보통 `128 + x` 로 계산된다. 여기서 `x` 는 프로세스를 죽인 signal 의 번호이다.
- 137: SIGKILL (128 + 9) - 137: SIGKILL (128 + 9)
- 143: SIGTERM (128 + 15) - 143: SIGTERM (128 + 15)
### 4.1.5 효과적인 liveness probe 만들기 ### 4.1.5 효과적인 liveness probe 만들기
배포할 때 필수! 설정하지 않으면 쿠버네티스는 컨테이너의 프로세스가 실행 중인지만 확인하여 컨테이너의 상태를 확인한다. 배포할 때 필수! 설정하지 않으면 쿠버네티스는 컨테이너의 프로세스가 실행 중인지만 확인하여 컨테이너의 상태를 확인한다.
- 앱에 health check API 를 만들어서 이를 호출하도록 할 수도 있다. - 앱에 health check API 를 만들어서 이를 호출하도록 할 수도 있다.
- 이 경우 앱 자신에 대한 health check 만 하도록 한다. 프론트 서버에서 DB 가 접속이 안된다는 이유로 실패했다고 하면 안된다. 이건 프론트 서버가 아닌 DB 의 문제일 수도 있다. - 이 경우 앱 자신에 대한 health check 만 하도록 한다. 프론트 서버에서 DB 가 접속이 안된다는 이유로 실패했다고 하면 안된다. 이건 프론트 서버가 아닌 DB 의 문제일 수도 있다.
- Liveness probe 는 가벼워야 한다. - Liveness probe 는 가벼워야 한다.
- 자원을 너무 많이 소모해서도 안되고, 지나치게 오래 걸려도 안된다. - 자원을 너무 많이 소모해서도 안되고, 지나치게 오래 걸려도 안된다.
- Liveness probe 구현 시 재시도 횟수를 포함하여 구현할 필요 없다. - Liveness probe 구현 시 재시도 횟수를 포함하여 구현할 필요 없다.
- `failureThreshold` 옵션을 이용한다. - `failureThreshold` 옵션을 이용한다.
## 4.2 ReplicationControllers ## 4.2 ReplicationControllers
--- ---
컨테이너가 죽으면 liveness probe 등을 이용하여 다시 재시작하면 된다. 이 작업은 pod 가 존재하고 있는 노드의 Kubelet 이 해준다. 그런데 만약 노드가 죽으면? Control Plane 이 직접 나서서 죽어버린 pod 를 다시 살려야 한다. 컨테이너가 죽으면 liveness probe 등을 이용하여 다시 재시작하면 된다. 이 작업은 pod 가 존재하고 있는 노드의 Kubelet 이 해준다. 그런데 만약 노드가 죽으면? Control Plane 이 직접 나서서 죽어버린 pod 를 다시 살려야 한다.
다만 직접 만든 pod 에 대해서는 다시 살릴 수 없다. 직접 만든 pod 에는 Control Plane 이 관여하지 않고, 노드에 있는 Kubelet 혼자서 관리하고 있기 때문에, 노드가 죽어서 Kubelet 이 사라지면 되살릴 방법이 없어진다. 다만 직접 만든 pod 에 대해서는 다시 살릴 수 없다. 직접 만든 pod 에는 Control Plane 이 관여하지 않고, 노드에 있는 Kubelet 혼자서 관리하고 있기 때문에, 노드가 죽어서 Kubelet 이 사라지면 되살릴 방법이 없어진다.
노드가 죽은 경우에도 다른 노드에서 pod 가 시작되게 하려면, ReplicationController 와 같은 쿠버네티스 리소스를 사용해야 한다. 노드가 죽은 경우에도 다른 노드에서 pod 가 시작되게 하려면, ReplicationController 와 같은 쿠버네티스 리소스를 사용해야 한다.
**근데 docs 보면 다음과 같은 내용이 있다!!!** **근데 docs 보면 다음과 같은 내용이 있다!!!**
> Note: A Deployment that configures a ReplicaSet is now the recommended way to set up replication. > Note: A Deployment that configures a ReplicaSet is now the recommended way to set up replication.
**ReplicationController** 는 pod 가 동작하고 있는지 확인하고, pod 의 복제본 개수를 정확하게 관리해준다. **ReplicationController** 는 pod 가 동작하고 있는지 확인하고, pod 의 복제본 개수를 정확하게 관리해준다.
### 4.2.1 The operation of a ReplicationController ### 4.2.1 The operation of a ReplicationController
ReplicationController 는 현재 실행 중인 (정상 작동 중인) pod 의 목록을 확인하여 특정 'type' 의 pod 개수가 일정하도록 관리해준다. 만약 pod 개수가 너무 적으면 pod 를 더 띄우고, 너무 많으면 pod 를 삭제한다. ReplicationController 는 현재 실행 중인 (정상 작동 중인) pod 의 목록을 확인하여 특정 'type' 의 pod 개수가 일정하도록 관리해준다. 만약 pod 개수가 너무 적으면 pod 를 더 띄우고, 너무 많으면 pod 를 삭제한다.
#### Pod 의 개수가 안 맞는 경우 #### Pod 의 개수가 안 맞는 경우
- `kubectl` 등 커맨드로 직접 같은 pod 를 하나 더 생성하는 경우 - `kubectl` 등 커맨드로 직접 같은 pod 를 하나 더 생성하는 경우
- 존재하는 pod 에서 'type' 을 변경하는 경우 - 존재하는 pod 에서 'type' 을 변경하는 경우
- ReplicationController 에서 필요한 pod 개수를 변경하는 경우 - ReplicationController 에서 필요한 pod 개수를 변경하는 경우
여기서 'type' 이라 함은 pod 들의 부분집합을 말하는데, label selector 를 사용하여 얻은 부분집합이다. 여기서 'type' 이라 함은 pod 들의 부분집합을 말하는데, label selector 를 사용하여 얻은 부분집합이다.
동작 방식은 간단하다. 정해진 label selector 에 맞는 pod 의 개수를 확인하여 정해진 만큼 있으면 아무 것도 하지 않는다. Pod 개수가 적거나 많으면 더 만들거나 삭제한다. 동작 방식은 간단하다. 정해진 label selector 에 맞는 pod 의 개수를 확인하여 정해진 만큼 있으면 아무 것도 하지 않는다. Pod 개수가 적거나 많으면 더 만들거나 삭제한다.
#### Three parts of a ReplicationController #### Three parts of a ReplicationController
- *label selector*: ReplicationController 의 scope (관리할 pod 들) 를 결정할 label - *label selector*: ReplicationController 의 scope (관리할 pod 들) 를 결정할 label
- *replica count*: 필요한 pod 의 개수를 정함 - *replica count*: 필요한 pod 의 개수를 정함
- *pod template*: 새로운 pod 를 만들 때 사용할 template - *pod template*: 새로운 pod 를 만들 때 사용할 template
이 세 가지는 ReplicationController 가 실행된 이후에도 수정이 가능하지만, replica count 를 수정하면 현재 실행 중인 pod 에 영향을 미치게 된다. 이 세 가지는 ReplicationController 가 실행된 이후에도 수정이 가능하지만, replica count 를 수정하면 현재 실행 중인 pod 에 영향을 미치게 된다.
Label selector 를 고치게 되면 ReplicationController 의 scope 에서 pod 이 빠지게 된다. (근데 그러면 변경된 label selector 에 따라서 새로운 pod 들이 scope 에 들어오게 되면 existing pod 에 영향을 주는 것 아닌가?) Label selector 를 고치게 되면 ReplicationController 의 scope 에서 pod 이 빠지게 된다. (근데 그러면 변경된 label selector 에 따라서 새로운 pod 들이 scope 에 들어오게 되면 existing pod 에 영향을 주는 것 아닌가?)
#### 궁금해서 테스트 해봤다. #### 궁금해서 테스트 해봤다.
- 우선 `app=kubia` label 이 달린 pod 3개를 생성하고, ReplicationController 를 만들어 `app=kubia2` label 을 가진 pod 를 2개 생성 - 우선 `app=kubia` label 이 달린 pod 3개를 생성하고, ReplicationController 를 만들어 `app=kubia2` label 을 가진 pod 를 2개 생성
- ReplicationController 에서 `template``label``app=kubia` 로 변경 - ReplicationController 에서 `template``label``app=kubia` 로 변경
- `kubectl apply -f` 를 이용해서 존재하는 object 를 수정 (`create` 쓰면 삭제되고 다시 만드니까) - `kubectl apply -f` 를 이용해서 존재하는 object 를 수정 (`create` 쓰면 삭제되고 다시 만드니까)
- 에러가 뜨며 수정이 불가능 - 에러가 뜨며 수정이 불가능
- 한번 ReplicationController 를 생성할 때 `selector` 가 정해졌는데, `template``label` 을 바꾸면 `selector` 와 값이 달라지게 되므로 에러 - 한번 ReplicationController 를 생성할 때 `selector` 가 정해졌는데, `template``label` 을 바꾸면 `selector` 와 값이 달라지게 되므로 에러
- 그럼 ReplicationController YAML 에서 `selector``app=kubia` 로 명시적으로 적기 (앞에서는 안적었음) - 그럼 ReplicationController YAML 에서 `selector``app=kubia` 로 명시적으로 적기 (앞에서는 안적었음)
- 다시 `kubectl apply -f` 를 하니 적용이 됐고, `app=kubia` 가 달려있던 pod 3개 중 하나가 `Terminating` 으로 변경됨 - 다시 `kubectl apply -f` 를 하니 적용이 됐고, `app=kubia` 가 달려있던 pod 3개 중 하나가 `Terminating` 으로 변경됨
- 그러면 existing pod 에 영향을 주는 것 같으니 책의 의도는 "기존 scope 에 있던 existing pod" 에 영향을 주지 않는 다는 말인 듯 (;;) - 그러면 existing pod 에 영향을 주는 것 같으니 책의 의도는 "기존 scope 에 있던 existing pod" 에 영향을 주지 않는 다는 말인 듯 (;;)
그리고 ReplicationController 는 pod 를 생성하고 나면 pod 의 내용 (컨테이너 이미지, 환경 변수 등) 은 신경쓰지 않기 때문에 pod template 을 수정해도 기존 pod 에는 영향을 주지 않는다. 그리고 ReplicationController 는 pod 를 생성하고 나면 pod 의 내용 (컨테이너 이미지, 환경 변수 등) 은 신경쓰지 않기 때문에 pod template 을 수정해도 기존 pod 에는 영향을 주지 않는다.
#### ReplicationController 의 장점 #### ReplicationController 의 장점
- 실행 중인 pod 의 개수를 일정하게 유지해준다. - 실행 중인 pod 의 개수를 일정하게 유지해준다.
- 클러스터 노드가 죽는 경우, 노드 안에 있던 pod 중 ReplicationController 의 scope 안에 있는 pod 를 살려내 준다. - 클러스터 노드가 죽는 경우, 노드 안에 있던 pod 중 ReplicationController 의 scope 안에 있는 pod 를 살려내 준다.
- Scale out 을 쉽게 해준다. - Scale out 을 쉽게 해준다.
### 4.2.2 ReplicationController 만들기 ### 4.2.2 ReplicationController 만들기
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: ReplicationController kind: ReplicationController
metadata: metadata:
name: sung-rc name: sung-rc
spec: spec:
replicas: 3 # 복제본 3개 replicas: 3 # 복제본 3개
selector: # label selector selector: # label selector
app: kubia app: kubia
template: # pod template template: # pod template
metadata: metadata:
labels: labels:
app: kubia app: kubia
spec: spec:
containers: containers:
- name: sung - name: sung
image: luksa/kubia image: luksa/kubia
ports: ports:
- containerPort: 8080 - containerPort: 8080
``` ```
`selector` 를 명시할 필요는 없다. `template``label` 을 보고 자동으로 뽑아준다. (`selector` 의 label 이 `spec``label` 과 달라서 컨테이너가 무한히 생성되는 문제를 막기 위해서 이를 검사한다) `selector` 를 명시할 필요는 없다. `template``label` 을 보고 자동으로 뽑아준다. (`selector` 의 label 이 `spec``label` 과 달라서 컨테이너가 무한히 생성되는 문제를 막기 위해서 이를 검사한다)
### 4.2.3 Seeing the ReplicationController in action ### 4.2.3 Seeing the ReplicationController in action
컨테이너를 하나 삭제해 보면 즉시 새로운 컨테이너가 하나 생기는 모습을 확인할 수 있다. 컨테이너를 하나 삭제해 보면 즉시 새로운 컨테이너가 하나 생기는 모습을 확인할 수 있다.
**동작 방식**: ReplicationController 는 scope 내의 pod 가 삭제되면 삭제된 사실을 알게 된다. 그러면 ReplicationController 는 pod 의 개수가 적절한지 확인하게 되고, 만약 적절하지 않으면 적당한 행동을 취해 pod 수를 조정한다. **동작 방식**: ReplicationController 는 scope 내의 pod 가 삭제되면 삭제된 사실을 알게 된다. 그러면 ReplicationController 는 pod 의 개수가 적절한지 확인하게 되고, 만약 적절하지 않으면 적당한 행동을 취해 pod 수를 조정한다.
`kubectl get rc` 를 하면 ReplicationController 별로 `DESIRED`, `CURRENT`, `READY` 컨테이너 수를 확인할 수 있다. `kubectl get rc` 를 하면 ReplicationController 별로 `DESIRED`, `CURRENT`, `READY` 컨테이너 수를 확인할 수 있다.
(노드를 죽이는 실습은 생략) (노드를 죽이는 실습은 생략)
### 4.2.4 ReplicationController 의 scope 조절하기 ### 4.2.4 ReplicationController 의 scope 조절하기
ReplicationController 가 생성한 pod 은 ReplicationController 와 직접 연결되어 있지 않다. 단지 ReplicationController 는 label selector 에 맞는 pod 들을 관리할 뿐이다. 그러므로 pod 의 label 을 변경하게 되면 scope 에 들어가거나 나갈 수 있다. ReplicationController 가 생성한 pod 은 ReplicationController 와 직접 연결되어 있지 않다. 단지 ReplicationController 는 label selector 에 맞는 pod 들을 관리할 뿐이다. 그러므로 pod 의 label 을 변경하게 되면 scope 에 들어가거나 나갈 수 있다.
Label 을 변경해서 scope 에서 나가게 되면 ReplicationController 를 사용하지 않고 만들어진 pod 과 다를 바가 없게 된다. 노드가 죽으면 끝이다. 또한 pod 를 관리하고 있던 ReplicationController 를 삭제하면 scope 를 벗어난 pod 는 제거되지 않는다. Label 을 변경해서 scope 에서 나가게 되면 ReplicationController 를 사용하지 않고 만들어진 pod 과 다를 바가 없게 된다. 노드가 죽으면 끝이다. 또한 pod 를 관리하고 있던 ReplicationController 를 삭제하면 scope 를 벗어난 pod 는 제거되지 않는다.
#### ReplicationController 의 label selector 수정하기 #### ReplicationController 의 label selector 수정하기
(아 혼자 테스트 해봤는데 내용이 책에 있었네) (아 혼자 테스트 해봤는데 내용이 책에 있었네)
ReplicationController 의 label selector 를 수정하게 되면 기존 scope 에 있던 pod 들은 모두 scope 에 포함되지 않게 되며, 새로운 label selector 를 이용해 조회한 pod 목록을 보고 ReplicationController 는 적절한 동작을 취한다. ReplicationController 의 label selector 를 수정하게 되면 기존 scope 에 있던 pod 들은 모두 scope 에 포함되지 않게 되며, 새로운 label selector 를 이용해 조회한 pod 목록을 보고 ReplicationController 는 적절한 동작을 취한다.
### 4.2.5 Pod template 수정하기 ### 4.2.5 Pod template 수정하기
Template 이 바뀐다고 해서 기존에 scope 에 있던 pod 들이 갑자기 사라지거나 하지는 않으므로, 기존 pod 를 하나 지우게 되면 ReplicationController 가 새로 pod 생성할 때 변경된 template 으로 생성한다. Template 이 바뀐다고 해서 기존에 scope 에 있던 pod 들이 갑자기 사라지거나 하지는 않으므로, 기존 pod 를 하나 지우게 되면 ReplicationController 가 새로 pod 생성할 때 변경된 template 으로 생성한다.
### 4.2.6 Horizontal scaling ### 4.2.6 Horizontal scaling
```bash ```bash
$ kubectl scale rc <RC_NAME> --replicas=<REPLICAS> $ kubectl scale rc <RC_NAME> --replicas=<REPLICAS>
``` ```
혹은 혹은
```bash ```bash
$ kubectl edit rc <RC_NAME> $ kubectl edit rc <RC_NAME>
``` ```
으로 YAML 파일을 직접 수정하여 scaling 을 할 수 있다. 개수만 바꿔주면 나머지는 ReplicationController 가 알아서 한다. 으로 YAML 파일을 직접 수정하여 scaling 을 할 수 있다. 개수만 바꿔주면 나머지는 ReplicationController 가 알아서 한다.
### 4.2.7 ReplicationController 삭제하기 ### 4.2.7 ReplicationController 삭제하기
`kubectl delete` 를 사용하면 scope 내의 pod 도 같이 지워진다! 단 `--cascade=false` 옵션을 주면 ReplicationController 만 지워지고 pod 는 남아있게 된다. `kubectl delete` 를 사용하면 scope 내의 pod 도 같이 지워진다! 단 `--cascade=false` 옵션을 주면 ReplicationController 만 지워지고 pod 는 남아있게 된다.
Pod 가 관리되지 않은 채로 남아있다고 하더라도, 다시 ReplicationController 를 생성해서 얼마든지 다시 관리할 수 있다. Pod 가 관리되지 않은 채로 남아있다고 하더라도, 다시 ReplicationController 를 생성해서 얼마든지 다시 관리할 수 있다.
## 4.3 ReplicaSet ## 4.3 ReplicaSet
--- ---
ReplicationControllers will eventually be deprecated! ReplicationControllers will eventually be deprecated!
보통 ReplicaSet 은 Deployment 를 생성할 때 자동으로 만들어진다. 보통 ReplicaSet 은 Deployment 를 생성할 때 자동으로 만들어진다.
### 4.3.1 ReplicaSet 과 ReplicationController 비교 ### 4.3.1 ReplicaSet 과 ReplicationController 비교
ReplicaSet 의 pod selector 가 표현력이 훨씬 강하다. ReplicationController 는 특정 label 을 포함하는지만 검사가 가능하지만, ReplicaSet 의 경우 label 이 없는지, 혹은 특정 label key 를 갖는지 검사할 수 있다. ReplicaSet 의 pod selector 가 표현력이 훨씬 강하다. ReplicationController 는 특정 label 을 포함하는지만 검사가 가능하지만, ReplicaSet 의 경우 label 이 없는지, 혹은 특정 label key 를 갖는지 검사할 수 있다.
### 4.3.2 ReplicaSet 정의하기 ### 4.3.2 ReplicaSet 정의하기
```yaml ```yaml
apiVersion: apps/v1 # apps API group 에 있다 apiVersion: apps/v1 # apps API group 에 있다
kind: ReplicaSet kind: ReplicaSet
metadata: metadata:
name: sung name: sung
spec: spec:
replicas: 3 replicas: 3
selector: # 이 부분만 다르다 selector: # 이 부분만 다르다
matchLabels: matchLabels:
app: sung app: sung
template: # ReplicationController 와 동일 template: # ReplicationController 와 동일
metadata: metadata:
labels: labels:
app: sung app: sung
spec: spec:
containers: containers:
- name: sung - name: sung
image: luksa/kubia image: luksa/kubia
``` ```
### 4.3.3 Creating and examining a ReplicaSet ### 4.3.3 Creating and examining a ReplicaSet
`kubectl get rs` 로 ReplicaSet 의 목록을 확인할 수 있다. `kubectl get rs` 로 ReplicaSet 의 목록을 확인할 수 있다.
### 4.3.4 More expressive label selectors ### 4.3.4 More expressive label selectors
`matchLabels` 는 ReplicationController 의 label selector 와 동일하다. `matchLabels` 는 ReplicationController 의 label selector 와 동일하다.
`matchExpressions` property 를 이용해서 더욱 복잡한 label selector 를 이용할 수 있다. `matchExpressions` property 를 이용해서 더욱 복잡한 label selector 를 이용할 수 있다.
```yaml ```yaml
selector: selector:
matchExpressions: matchExpressions:
- key: app - key: app
operator: In operator: In
values: values:
- sung - sung
``` ```
각 expression 에는 세 가지 값이 필요하다. `key`, `operator`, `values` (optional). 각 expression 에는 세 가지 값이 필요하다. `key`, `operator`, `values` (optional).
- `key`: label 의 key 값 - `key`: label 의 key 값
- `operator`: 네 개가 있다. - `operator`: 네 개가 있다.
- `In`: label 의 value 가 `values` 안에 있는지 검사 - `In`: label 의 value 가 `values` 안에 있는지 검사
- `NotIn`: label 의 value 가 `values` 안에 없는지 검사 - `NotIn`: label 의 value 가 `values` 안에 없는지 검사
- `Exists`: `key` 를 갖고 있는지 검사 (`values` 옵션 불필요) - `Exists`: `key` 를 갖고 있는지 검사 (`values` 옵션 불필요)
- `DoesNotExist`: `key` 를 갖고 있지 않은지 검사 (`values` 옵션 불필요) - `DoesNotExist`: `key` 를 갖고 있지 않은지 검사 (`values` 옵션 불필요)
여러 개의 expression 을 사용하면 모든 expression 을 만족해야 pod 가 선택된다. 여러 개의 expression 을 사용하면 모든 expression 을 만족해야 pod 가 선택된다.
### 4.3.5 ReplicaSet 정리 ### 4.3.5 ReplicaSet 정리
항상 ReplicationController 대신 ReplicaSet 을 사용해라! 항상 ReplicationController 대신 ReplicaSet 을 사용해라!
## 4.4 DaemonSet 을 이용하여 한 노드에 pod 하나씩 실행하기 ## 4.4 DaemonSet 을 이용하여 한 노드에 pod 하나씩 실행하기
--- ---
ReplicationController 나 ReplicaSet 을 사용하면 클러스터 내의 임의의 노드에서 실행하게 되는데, 각 노드마다 하나씩 존재해야 하는 앱이 있을 수도 있다. (한 노드가 여유롭다고 몰리면 안된다) 대표적으로 노드의 리소스를 확인하거나 노드의 로그를 확인하고 싶은 경우가 그렇다. ReplicationController 나 ReplicaSet 을 사용하면 클러스터 내의 임의의 노드에서 실행하게 되는데, 각 노드마다 하나씩 존재해야 하는 앱이 있을 수도 있다. (한 노드가 여유롭다고 몰리면 안된다) 대표적으로 노드의 리소스를 확인하거나 노드의 로그를 확인하고 싶은 경우가 그렇다.
쿠버네티스를 사용하지 않는다면 system init script 나 systemd 를 사용할 것이고, 노드에서 이것들을 사용할 수 있긴 하지만, 쿠버네티스의 장점은 누릴 수 없게 된다. 쿠버네티스를 사용하지 않는다면 system init script 나 systemd 를 사용할 것이고, 노드에서 이것들을 사용할 수 있긴 하지만, 쿠버네티스의 장점은 누릴 수 없게 된다.
### 4.4.1 DaemonSet 으로 모든 노드에 pod 띄우기 ### 4.4.1 DaemonSet 으로 모든 노드에 pod 띄우기
ReplicaSet 과 거의 비슷하지만 DaemonSet 을 사용하면 Kubernetes Scheduler 의 통제를 받지 않게된다. DaemonSet 은 노드 개수 만큼 pod 을 만들며, 각 노드마다 pod 를 하나씩 올린다. ReplicaSet 과 거의 비슷하지만 DaemonSet 을 사용하면 Kubernetes Scheduler 의 통제를 받지 않게된다. DaemonSet 은 노드 개수 만큼 pod 을 만들며, 각 노드마다 pod 를 하나씩 올린다.
DaemonSet 은 replica count 의 개념이 없으며, 단지 모든 노드에 pod 가 하나씩 동작하고 있는지만 관리한다. 그러므로 노드가 죽으면 아무 일도 하지 않고, 노드가 새로 생길 때는 바로 pod 를 하나 더 띄워준다. DaemonSet 은 replica count 의 개념이 없으며, 단지 모든 노드에 pod 가 하나씩 동작하고 있는지만 관리한다. 그러므로 노드가 죽으면 아무 일도 하지 않고, 노드가 새로 생길 때는 바로 pod 를 하나 더 띄워준다.
### 4.4.2 DaemonSet 으로 특정 노드에 pod 띄우기 ### 4.4.2 DaemonSet 으로 특정 노드에 pod 띄우기
Node selector property 를 이용하면 모든 노드가 아니라 특정 노드에만 pod 를 띄울 수 있게 된다. Node selector property 를 이용하면 모든 노드가 아니라 특정 노드에만 pod 를 띄울 수 있게 된다.
주의사항: 노드는 unschedulable 상태로 만들 수 있는데, DaemonSet 은 이런 상태를 무시하고 pod 를 띄운다. 이 unschedulable property 는 scheduler 가 이용하는데, DaemonSet 은 scheduler 의 영향을 받지 않기 때문이다. 더불어 이 동작은 의도된 것이다. DaemonSet 은 시스템과 관련된 서비스를 운영할 때 사용하므로 unschedulable node 에도 pod 를 띄워야 한다. 주의사항: 노드는 unschedulable 상태로 만들 수 있는데, DaemonSet 은 이런 상태를 무시하고 pod 를 띄운다. 이 unschedulable property 는 scheduler 가 이용하는데, DaemonSet 은 scheduler 의 영향을 받지 않기 때문이다. 더불어 이 동작은 의도된 것이다. DaemonSet 은 시스템과 관련된 서비스를 운영할 때 사용하므로 unschedulable node 에도 pod 를 띄워야 한다.
YAML 파일은 ReplicaSet 과 거의 동일한데, node selector 를 사용한다는 차이점이 있다. YAML 파일은 ReplicaSet 과 거의 동일한데, node selector 를 사용한다는 차이점이 있다.
```yaml ```yaml
template: template:
... ...
spec: spec:
nodeSelctor: # node selector 를 사용한다. nodeSelctor: # node selector 를 사용한다.
disk: ssd disk: ssd
``` ```
## 4.5 Running pods that perform a single completable task ## 4.5 Running pods that perform a single completable task
--- ---
여기서 single completable task 란, 동작을 모두 마치면 종료하는 작업을 말하는 것이다. (기존에는 서버와 같이 계속 실행 중이어야 하는 프로세스를 다뤄왔다. 이 프로세스들은 완료 - 'completed' 의 개념이 없다) 여기서 single completable task 란, 동작을 모두 마치면 종료하는 작업을 말하는 것이다. (기존에는 서버와 같이 계속 실행 중이어야 하는 프로세스를 다뤄왔다. 이 프로세스들은 완료 - 'completed' 의 개념이 없다)
### 4.5.1 Job 리소스 ### 4.5.1 Job 리소스
Job 리소스를 사용하면 작업이 완료된 뒤에 재시작 되지 않는 pod 를 만들 수 있게 된다. Job 리소스를 사용하면 작업이 완료된 뒤에 재시작 되지 않는 pod 를 만들 수 있게 된다.
만약 노드가 죽는 경우에는 다른 노드로 다시 스케줄링 되며, 만약 컨테이너의 프로세스가 문제가 생긴 경우 컨테이너를 재시작할지 그냥 종료할지 선택할 수 있다. 만약 노드가 죽는 경우에는 다른 노드로 다시 스케줄링 되며, 만약 컨테이너의 프로세스가 문제가 생긴 경우 컨테이너를 재시작할지 그냥 종료할지 선택할 수 있다.
Job 은 정상적으로 종료되어야 하는 task 에 사용하는 것이 좋다. 쿠버네티스나 Job 을 사용하지 않고도 작업을 할 수 있겠지만, 쿠버네티스를 사용하면 노드가 죽거나 에러가 생기는 경우에 쿠버네티스의 기능을 활용하여 작업이 정상적으로 완료될 때까지 다시 실행할 수 있게 된다. Job 은 정상적으로 종료되어야 하는 task 에 사용하는 것이 좋다. 쿠버네티스나 Job 을 사용하지 않고도 작업을 할 수 있겠지만, 쿠버네티스를 사용하면 노드가 죽거나 에러가 생기는 경우에 쿠버네티스의 기능을 활용하여 작업이 정상적으로 완료될 때까지 다시 실행할 수 있게 된다.
### 4.5.2 Job 리소스 정의하기 ### 4.5.2 Job 리소스 정의하기
```yaml ```yaml
apiVersion: batch/v1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: batch-job name: batch-job
spec: spec:
template: template:
metadata: metadata:
labels: labels:
app: batch-job app: batch-job
spec: spec:
restartPolicy: OnFailure # 문제가 생겼을 때 어떻게 할지 restartPolicy: OnFailure # 문제가 생겼을 때 어떻게 할지
containers: containers:
- name: main - name: main
image: luksa/batch-job image: luksa/batch-job
``` ```
`restartPolicy` 는 일반적으로 `Always` 이다. 하지만 Job 리소스를 만들 때 이 값을 사용할 수는 없고, `OnFailure` 혹은 `Never` 를 사용하게 된다. (계속 실행할 거면 Job 을 쓰지 말자) 이 옵션 값을 사용하므로 작업이 완료된 뒤 컨테이너가 재시작되지 않는다. `restartPolicy` 는 일반적으로 `Always` 이다. 하지만 Job 리소스를 만들 때 이 값을 사용할 수는 없고, `OnFailure` 혹은 `Never` 를 사용하게 된다. (계속 실행할 거면 Job 을 쓰지 말자) 이 옵션 값을 사용하므로 작업이 완료된 뒤 컨테이너가 재시작되지 않는다.
### 4.5.3 Seeing a Job run a pod ### 4.5.3 Seeing a Job run a pod
Job 을 생성한 뒤 완료되면 pod 의 상태가 `Completed` 로 변경되고, 삭제되지 않은 채로 남아있다. 실행 결과 로그를 확인할 수 있게 하기 위해서이다. 단, Job 을 삭제하면 pod 도 같이 삭제된다. Job 을 생성한 뒤 완료되면 pod 의 상태가 `Completed` 로 변경되고, 삭제되지 않은 채로 남아있다. 실행 결과 로그를 확인할 수 있게 하기 위해서이다. 단, Job 을 삭제하면 pod 도 같이 삭제된다.
### 4.5.4 Running multiple pod instances in a job ### 4.5.4 Running multiple pod instances in a job
Job 은 꼭 하나의 pod 만 생성해야 하는 것은 아니다. 순차적 실행, 병렬 실행도 가능하다. Job 은 꼭 하나의 pod 만 생성해야 하는 것은 아니다. 순차적 실행, 병렬 실행도 가능하다.
#### 순차적 실행 #### 순차적 실행
```yaml ```yaml
... ...
spec: spec:
completions: 5 # 5번 완료되어야 Job 이 완전히 끝난다 completions: 5 # 5번 완료되어야 Job 이 완전히 끝난다
``` ```
`completions` 옵션을 사용하면 지정된 횟수만큼 pod 을 만들어서 작업을 수행한다. 한 번에 pod 하나씩 만들어서 작업을 수행하지만 중간에 작업이 실패하는 경우가 있을 수도 있으므로 총 생성하는 pod 개수는 지정된 값보다 많을 수 있다. `completions` 옵션을 사용하면 지정된 횟수만큼 pod 을 만들어서 작업을 수행한다. 한 번에 pod 하나씩 만들어서 작업을 수행하지만 중간에 작업이 실패하는 경우가 있을 수도 있으므로 총 생성하는 pod 개수는 지정된 값보다 많을 수 있다.
(연속으로 성공해야 하는지? 2개 완료하고 죽으면 처음부터 다시하나? 맥락 상 처음부터 다시 하지는 않을 것 같은데 - 찾아보기) (연속으로 성공해야 하는지? 2개 완료하고 죽으면 처음부터 다시하나? 맥락 상 처음부터 다시 하지는 않을 것 같은데 - 찾아보기)
#### 병렬 실행 #### 병렬 실행
```yaml ```yaml
... ...
spec: spec:
completions: 5 completions: 5
parallelism: 2 # 한 번에 만들 pod 개수 parallelism: 2 # 한 번에 만들 pod 개수
``` ```
`parallelism` 옵션을 사용하면 작업을 동시에 실행할 pod 개수를 설정할 수 있다. 여러 개 중 어느 하나라도 완료되면 바로 다음 pod 를 생성하여 `completions` 횟수만큼 작업을 완료한다. 이 값은 Job 이 실행 중일 때도 변경 가능하다. `parallelism` 옵션을 사용하면 작업을 동시에 실행할 pod 개수를 설정할 수 있다. 여러 개 중 어느 하나라도 완료되면 바로 다음 pod 를 생성하여 `completions` 횟수만큼 작업을 완료한다. 이 값은 Job 이 실행 중일 때도 변경 가능하다.
```bash ```bash
$ kubectl scale job <JOB_NAME> --replicas <NUM_REPLICAS> $ kubectl scale job <JOB_NAME> --replicas <NUM_REPLICAS>
``` ```
### 4.5.5 Job pod 에 제한 시간 걸기 ### 4.5.5 Job pod 에 제한 시간 걸기
작업이 끝날 때까지 얼마나 기다려야 할지 정할 수 있다. 중간에 무한 루프에 빠지는 등 멈출 수도 있다. 작업이 끝날 때까지 얼마나 기다려야 할지 정할 수 있다. 중간에 무한 루프에 빠지는 등 멈출 수도 있다.
`.spec.activeDeadlineSeconds` 옵션을 이용하면 지정된 시간 만큼 작업을 실행해보고, 시간이 지나도 완료되지 않으면 실패 처리한다. `.spec.activeDeadlineSeconds` 옵션을 이용하면 지정된 시간 만큼 작업을 실행해보고, 시간이 지나도 완료되지 않으면 실패 처리한다.
더불어 `.spec.backoffLimit` 옵션을 사용하면 최대 몇 번까지 작업을 재시도할지 설정할 수 있다. 다만 `activeDeadlineSeconds` 가 더 우선 순위가 높아 시간이 초과되면 재시도 횟수가 남아도 그냥 실패 처리한다. 더불어 `.spec.backoffLimit` 옵션을 사용하면 최대 몇 번까지 작업을 재시도할지 설정할 수 있다. 다만 `activeDeadlineSeconds` 가 더 우선 순위가 높아 시간이 초과되면 재시도 횟수가 남아도 그냥 실패 처리한다.
## 4.6 Scheduling Jobs to run periodically or once in the future ## 4.6 Scheduling Jobs to run periodically or once in the future
--- ---
### 4.6.1 CronJob 만들기 ### 4.6.1 CronJob 만들기
```yaml ```yaml
apiVersion: batch/v1beta1 apiVersion: batch/v1beta1
kind: CronJob kind: CronJob
metadata: metadata:
name: batch-job-15m name: batch-job-15m
spec: spec:
schedule: "0,15,30,45 * * * *" # 15분마다 실행하기 schedule: "0,15,30,45 * * * *" # 15분마다 실행하기
jobTemplate: jobTemplate:
spec: spec:
template: template:
metadata: metadata:
labels: labels:
app: periodic-batch-job app: periodic-batch-job
spec: spec:
restartPolicy: OnFailure restartPolicy: OnFailure
containers: containers:
- name: main - name: main
image: luksa/batch-job image: luksa/batch-job
``` ```
### 4.6.2 Understanding how scheduled jobs are run ### 4.6.2 Understanding how scheduled jobs are run
대략 Job 을 실행할 시간대가 되면 CronJob 이 Job 리소스를 생성하고, Job 이 pod 를 만들어서 작업을 실행한다. 대략 Job 을 실행할 시간대가 되면 CronJob 이 Job 리소스를 생성하고, Job 이 pod 를 만들어서 작업을 실행한다.
이 생성 작업이 조금 걸릴 수도 있으므로, `.spec.startingDeadlineSeconds` 옵션을 설정하여 정해진 시간으로부터 늦어도 몇 초 이내에 작업이 실행되어야 하는지 설정할 수 있다. 이 생성 작업이 조금 걸릴 수도 있으므로, `.spec.startingDeadlineSeconds` 옵션을 설정하여 정해진 시간으로부터 늦어도 몇 초 이내에 작업이 실행되어야 하는지 설정할 수 있다.
만약에 정해진 시간이 지나도 실행하지 못했다면 자동으로 실패처리 된다. 만약에 정해진 시간이 지나도 실행하지 못했다면 자동으로 실패처리 된다.
CronJob 은 보통 1회 실행 시 Job 를 하나 만들지만, 2개 이상이 생성될 수도 있으므로, idempotency 를 가지고 있어야 한다. 여러 번 실행해도 같은 결과를 보장해야 한다. 또한 직전에 실패한 경우가 있다면 다음 Job 은 직전에 실패한 것까지 고려해야할 수도 있다. CronJob 은 보통 1회 실행 시 Job 를 하나 만들지만, 2개 이상이 생성될 수도 있으므로, idempotency 를 가지고 있어야 한다. 여러 번 실행해도 같은 결과를 보장해야 한다. 또한 직전에 실패한 경우가 있다면 다음 Job 은 직전에 실패한 것까지 고려해야할 수도 있다.
--- ---
## Discussion & Additional Topics ## Discussion & Additional Topics
### Actual examples on usage of DaemonSets ### Actual examples on usage of DaemonSets
### More about unix signals ### More about unix signals
- https://jhnyang.tistory.com/143 - https://jhnyang.tistory.com/143
### More about exit codes ### More about exit codes
### Docker also has restart policy ### Docker also has restart policy
- https://docs.docker.com/config/containers/start-containers-automatically/ - https://docs.docker.com/config/containers/start-containers-automatically/
### What happens when the Kubernetes master plane fails? ### What happens when the Kubernetes master plane fails?
- https://stackoverflow.com/questions/39172131/what-happens-when-the-kubernetes-master-fails - https://stackoverflow.com/questions/39172131/what-happens-when-the-kubernetes-master-fails
### Can a single pod be controlled by two ReplicaSets? ### Can a single pod be controlled by two ReplicaSets?
- 만약 레이블이 2개인 pod 이 하나가 있고 그 이후에 ReplicaSet 을 만든다면...? - 만약 레이블이 2개인 pod 이 하나가 있고 그 이후에 ReplicaSet 을 만든다면...?
### Pod garbage collector? ### Pod garbage collector?
- https://kubernetes.io/ko/docs/concepts/workloads/controllers/garbage-collection/ - https://kubernetes.io/ko/docs/concepts/workloads/controllers/garbage-collection/

View File

@@ -1,470 +1,477 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops] tags: [kubernetes, sre, devops]
title: "05. Services: Enabling Clients to Discover and Talk to Pods" title: "05. Services: Enabling Clients to Discover and Talk to Pods"
date: "2021-04-07" date: "2021-04-07"
github_title: "2021-04-07-05-services" github_title: "2021-04-07-05-services"
image: image:
path: /assets/img/posts/k8s-05.jpeg path: /assets/img/posts/k8s-05.jpeg
--- ---
![k8s-05.jpeg](../../../assets/img/posts/k8s-05.jpeg) _Using `kubectl exec` to test out a connection to the service by running curl in one of the pods. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-5)_ ![k8s-05.jpeg](../../../assets/img/posts/k8s-05.jpeg) _Using `kubectl exec` to test out a connection to the service by running curl in one of the pods. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-5)_
많은 앱들이 request (요청) 을 받아 서비스를 제공하는 형태인데, 이런 요청을 보내려면 IP 주소를 알아야 한다. 한편 Kubernetes 를 사용하게 되면 pod 의 IP 주소를 알아야 하는데, Kubernetes 의 pod 들은 굉장히 동적이므로 이들의 IP 주소를 알아낼 방법이 필요하다. 많은 앱들이 request (요청) 을 받아 서비스를 제공하는 형태인데, 이런 요청을 보내려면 IP 주소를 알아야 한다. 한편 Kubernetes 를 사용하게 되면 pod 의 IP 주소를 알아야 하는데, Kubernetes 의 pod 들은 굉장히 동적이므로 이들의 IP 주소를 알아낼 방법이 필요하다.
Pod 들은 스케쥴링 되고 스케일링 되기 때문에 IP 주소가 자주 바뀔 수 있으며, pod 가 시작된 후 IP 주소가 할당될 뿐만 아니라, 하나의 서비스를 위해 pod 를 여러 개 사용하는 경우 하나의 IP 를 사용하여 여러 개의 pod 에 접근할 방법이 필요하다. (쿠버네티스를 사용하지 않으면 고정 IP 등을 사용하거나 따로 설정된 주소로 요청이 가도록 했을 것이다) Pod 들은 스케쥴링 되고 스케일링 되기 때문에 IP 주소가 자주 바뀔 수 있으며, pod 가 시작된 후 IP 주소가 할당될 뿐만 아니라, 하나의 서비스를 위해 pod 를 여러 개 사용하는 경우 하나의 IP 를 사용하여 여러 개의 pod 에 접근할 방법이 필요하다. (쿠버네티스를 사용하지 않으면 고정 IP 등을 사용하거나 따로 설정된 주소로 요청이 가도록 했을 것이다)
## 5.1 Services 소개 ## 5.1 Services 소개
---
Service 를 이용하면 같은 일을 하는 여러 개의 pod 에 하나의 entrypoint 를 제공할 수 있다.
Service 를 이용하면 같은 일을 하는 여러 개의 pod 에 하나의 entrypoint 를 제공할 수 있다.
Service 는 IP 와 포트가 설정되어 있고 (service 가 살아있는 동안) 고정되어 있다. 자신의 주소로 요청이 들어오면 연결된 pod 중 하나로 요청을 보내준다. (로드 밸런싱도 가능)
Service 는 IP 와 포트가 설정되어 있고 (service 가 살아있는 동안) 고정되어 있다. 자신의 주소로 요청이 들어오면 연결된 pod 중 하나로 요청을 보내준다. (로드 밸런싱도 가능)
### 5.1.1 Service 만들기
### 5.1.1 Service 만들기
Service 에 포함되는 pod 를 정의하기 위해서는 **label selector** 를 사용한다.
Service 에 포함되는 pod 를 정의하기 위해서는 **label selector** 를 사용한다.
```yaml
apiVersion: v1 ```yaml
kind: Service apiVersion: v1
metadata: kind: Service
name: sung metadata:
spec: name: sung
ports: spec:
- port: 80 ports:
targetPort: 8080 - port: 80
selector: targetPort: 8080
app: kubia selector:
``` app: kubia
```
Service 가 실행했을 때 할당되는 IP 주소의 80 포트로 들어오는 요청은 `app=kubia` label 을 가진 pod 의 8080 포트로 요청이 전달된다.
Service 가 실행했을 때 할당되는 IP 주소의 80 포트로 들어오는 요청은 `app=kubia` label 을 가진 pod 의 8080 포트로 요청이 전달된다.
`kubectl create` 로 service 를 생성한 후, `kubectl get svc` 를 하면 service 목록을 확인할 수 있다. Column 중에 `CLUSTER-IP` 가 있는데 이는 클러스터 내부에서 사용하는 IP 주소이다. (`EXTERNAL-IP` 는 외부에서 사용하는 주소인데 일단은 `<none>` 이다.)
`kubectl create` 로 service 를 생성한 후, `kubectl get svc` 를 하면 service 목록을 확인할 수 있다. Column 중에 `CLUSTER-IP` 가 있는데 이는 클러스터 내부에서 사용하는 IP 주소이다. (`EXTERNAL-IP` 는 외부에서 사용하는 주소인데 일단은 `<none>` 이다.)
원래 service 의 주 목적은 클러스터 내의 pod 들을 다른 pod 들에게 보여주는 것이므로 cluster IP 가 존재한다. (프론트, 백 각각 service 로 만들어 두면 프론트에서 백엔드 service 에 접근할 수 있어야 한다) 하지만 주로 클러스터 외부로 expose 하는 경우가 더 많을 것이다.
원래 service 의 주 목적은 클러스터 내의 pod 들을 다른 pod 들에게 보여주는 것이므로 cluster IP 가 존재한다. (프론트, 백 각각 service 로 만들어 두면 프론트에서 백엔드 service 에 접근할 수 있어야 한다) 하지만 주로 클러스터 외부로 expose 하는 경우가 더 많을 것이다.
#### Session affinity
#### Session affinity
4장에서 사용한 ReplicationController 를 생성하고 service 를 만든 뒤 service 의 주소로 `curl` 을 날려보면 항상 같은 pod 가 요청을 처리하지는 않는다는 것을 확인할 수 있다. 만약에 같은 client 의 요청은 같은 pod 에서 처리하고 싶다면 `.spec.sessionAffinity` 를 설정하면 된다.
4장에서 사용한 ReplicationController 를 생성하고 service 를 만든 뒤 service 의 주소로 `curl` 을 날려보면 항상 같은 pod 가 요청을 처리하지는 않는다는 것을 확인할 수 있다. 만약에 같은 client 의 요청은 같은 pod 에서 처리하고 싶다면 `.spec.sessionAffinity` 를 설정하면 된다.
Service 의 `sessionAffinity` 는 `None` 과 `ClientIP` 만 지정할 수 있다. `ClientIP` 로 지정하게 되면 같은 client 의 요청은 하나의 pod 에서 무조건 처리하게 된다.
Service 의 `sessionAffinity``None``ClientIP` 만 지정할 수 있다. `ClientIP` 로 지정하게 되면 같은 client 의 요청은 하나의 pod 에서 무조건 처리하게 된다.
Cookie 를 사용한 affinity 설정은 불가능하다. Service 가 애초에 TCP/UDP 레벨 (transport layer)에 있어서 Cookie 가 있는 HTTP 레벨 (application layer) 과 다르다.
Cookie 를 사용한 affinity 설정은 불가능하다. Service 가 애초에 TCP/UDP 레벨 (transport layer)에 있어서 Cookie 가 있는 HTTP 레벨 (application layer) 과 다르다.
#### 여러 개의 포트 개방
#### 여러 개의 포트 개방
여러 개의 포트를 개방하여 pod 의 포트로 redirect 시킬 수 있다. 다만 각 포트에 해당하는 이름을 설정해줘야 한다.
여러 개의 포트를 개방하여 pod 의 포트로 redirect 시킬 수 있다. 다만 각 포트에 해당하는 이름을 설정해줘야 한다.
```yaml
... ```yaml
spec: ...
ports: spec:
- name: http # 이름을 지정해줘야 한다 ports:
port: 80 - name: http # 이름을 지정해줘야 한다
targetPort: 8080 port: 80
- name: https targetPort: 8080
port: 443 - name: https
targetPort: 8443 port: 443
selector: targetPort: 8443
app: kubia selector:
``` app: kubia
```
또한 label selector 는 포트마다 다르게 설정할 수 없다. 포트마다 다른 label selector 를 사용하고 싶다면 service 를 여러 개 만들어야 한다.
또한 label selector 는 포트마다 다르게 설정할 수 없다. 포트마다 다른 label selector 를 사용하고 싶다면 service 를 여러 개 만들어야 한다.
#### 포트에 이름 붙이기
#### 포트에 이름 붙이기
애플리케이션이 복잡해지면 포트를 변경할 일이 생길 수도 있다. 포트 번호를 단순히 숫자로 관리하면 나중에 pod 레벨에서 port 가 변경되었을 때 이를 참조하는 모든 쿠버네티스 오브젝트의 spec 에서 포트 번호를 변경해야 할 수도 있다. 그래서 pod 정의시 포트에 이름을 붙일 수 있다.
애플리케이션이 복잡해지면 포트를 변경할 일이 생길 수도 있다. 포트 번호를 단순히 숫자로 관리하면 나중에 pod 레벨에서 port 가 변경되었을 때 이를 참조하는 모든 쿠버네티스 오브젝트의 spec 에서 포트 번호를 변경해야 할 수도 있다. 그래서 pod 정의시 포트에 이름을 붙일 수 있다.
```yaml
kind: Pod ```yaml
spec: kind: Pod
containers: spec:
- name: kubia containers:
ports: - name: kubia
- name: http # 포트에 이름 붙이기 ports:
containerPort: 8080 - name: http # 포트에 이름 붙이기
- name: https containerPort: 8080
containerPort: 8443 - name: https
``` containerPort: 8443
```
Service 의 YAML 파일에서는 아래와 같이 사용하면 된다.
Service 의 YAML 파일에서는 아래와 같이 사용하면 된다.
```yaml
kind: Service ```yaml
spec: kind: Service
ports: spec:
- name: http ports:
port: 80 - name: http
targetPort: http port: 80
- name: https targetPort: http
port: 443 - name: https
targetPort: https port: 443
``` targetPort: https
```
Service 는 label selector 에 match 된 pod 중 임의의 한 pod 에 요청을 보낼텐데, 그 pod 의 정의에 포트 이름이 정의되어 있으면 그 포트 번호로 요청을 보내는 듯하다.
Service 는 label selector 에 match 된 pod 중 임의의 한 pod 에 요청을 보낼텐데, 그 pod 의 정의에 포트 이름이 정의되어 있으면 그 포트 번호로 요청을 보내는 듯하다.
그러므로 이런 것도 가능하다. 예전에 만든 pod 에는 8080 포트에 이름 `http` 가 있었는데, 새로 만든 pod 에는 80 포트에 `http` 를 붙여놓았다면, (같은 일을 하는 pod 라고 하자) service 에서는 `http` 라는 이름만 이용해서 pod 에게 요청을 전달하므로 실제 포트 번호는 다르게 설정할 수도 있다.
그러므로 이런 것도 가능하다. 예전에 만든 pod 에는 8080 포트에 이름 `http` 가 있었는데, 새로 만든 pod 에는 80 포트에 `http` 를 붙여놓았다면, (같은 일을 하는 pod 라고 하자) service 에서는 `http` 라는 이름만 이용해서 pod 에게 요청을 전달하므로 실제 포트 번호는 다르게 설정할 수도 있다.
### 5.1.2 Discovering services
### 5.1.2 Discovering services
Service 또한 pod 처럼 생성이 되어야 IP 주소가 할당되므로, pod 와 같은 문제를 갖고있다. Service 를 생성하고 그 IP 를 pod 에 하드코딩 하는 것도 바람직하지는 않아 보인다.
Service 또한 pod 처럼 생성이 되어야 IP 주소가 할당되므로, pod 와 같은 문제를 갖고있다. Service 를 생성하고 그 IP 를 pod 에 하드코딩 하는 것도 바람직하지는 않아 보인다.
특정 service 가 필요한 pod 들에게 service 의 IP 주소를 알려줄 방법이 필요하다.
특정 service 가 필요한 pod 들에게 service 의 IP 주소를 알려줄 방법이 필요하다.
#### 환경 변수 사용
#### 환경 변수 사용
쿠버네티스는 pod 가 생성될 때 현재 존재하고 있는 각 service 를 가리키는 환경 변수를 설정한다.
쿠버네티스는 pod 가 생성될 때 현재 존재하고 있는 각 service 를 가리키는 환경 변수를 설정한다.
그래서 service 를 먼저 만들고 이를 필요로 하는 pod 를 생성하면 환경 변수에서 service 의 IP 주소를 알 수 있다.
그래서 service 를 먼저 만들고 이를 필요로 하는 pod 를 생성하면 환경 변수에서 service 의 IP 주소를 알 수 있다.
단, 이렇게 하면 의존성이 생기고, 쿠버네티스 리소스를 띄워야 하는 순서가 생겨버린다.
단, 이렇게 하면 의존성이 생기고, 쿠버네티스 리소스를 띄워야 하는 순서가 생겨버린다.
#### DNS, FQDN
#### DNS, FQDN
쿠버네티스에도 DNS 서버가 있어서, 각 service 는 내부 DNS 서버에 등록된다. 그래서 pod 가 만약 service 의 이름을 알면 FQDN 을 이용해서 접근할 수 있게 된다.
쿠버네티스에도 DNS 서버가 있어서, 각 service 는 내부 DNS 서버에 등록된다. 그래서 pod 가 만약 service 의 이름을 알면 FQDN 을 이용해서 접근할 수 있게 된다.
FQDN 은 대략 이런 모양이다.
FQDN 은 대략 이런 모양이다.
```
<service_name>.<namespace>.svc.cluster.local ```
``` <service_name>.<namespace>.svc.cluster.local
```
단, 이렇게 하더라도 service 가 listen 하고있는 포트는 알아야 한다. (이건 IP 주소 알아내는 것보다는 쉬울 듯)
단, 이렇게 하더라도 service 가 listen 하고있는 포트는 알아야 한다. (이건 IP 주소 알아내는 것보다는 쉬울 듯)
만약 namespace 가 같다면, service 의 이름만 가지고서도 접근이 가능하다!
만약 namespace 가 같다면, service 의 이름만 가지고서도 접근이 가능하다!
#### Service IP 로 `ping` 이 불가능한 이유
#### Service IP 로 `ping` 이 불가능한 이유
`curl` 은 되지만 `ping` 은 안되는 이유는 cluster IP 가 virtual IP 라서 포트와 결합되었을 때 의미를 갖기 때문이다.
`curl` 은 되지만 `ping` 은 안되는 이유는 cluster IP 가 virtual IP 라서 포트와 결합되었을 때 의미를 갖기 때문이다.
정확히는, `ping` 명령어는 ICMP 위에서 동작한다. 그런데 service 의 클러스터 내부에서 특정 포트만 사용하기 때문에 ICMP request 에 응답하지 않으므로 `ping` 이 불가능하다.
정확히는, `ping` 명령어는 ICMP 위에서 동작한다. 그런데 service 의 클러스터 내부에서 특정 포트만 사용하기 때문에 ICMP request 에 응답하지 않으므로 `ping` 이 불가능하다.
## 5.2 클러스터 외부의 service 에 접속하기
## 5.2 클러스터 외부의 service 에 접속하기
### 5.2.1 Service endpoints ---
Service 와 pod 은 직접 연결되지 않고 사이에 endpoints 라는 리소스가 있다. `kubectl describe svc <SVC_NAME>` 을 실행해보면 `Endpoints: ...` 라면서 IP 주소와 포트가 나열되어있는 것을 확인할 수 있다. ### 5.2.1 Service endpoints
Pod selector 가 YAML 파일에 정의되어 있기는 하지만, 실제로 service 가 요청을 처리할 때는 pod selector 를 사용하지 않고 endpoints 에 있는 값들을 사용한다. Selector 는 endpoints 의 값들을 만들 때 사용한다. Service 와 pod 은 직접 연결되지 않고 사이에 endpoints 라는 리소스가 있다. `kubectl describe svc <SVC_NAME>` 을 실행해보면 `Endpoints: ...` 라면서 IP 주소와 포트가 나열되어있는 것을 확인할 수 있다.
그럼 endpoints 의 값들은 언제 갱신하는데? Pod 이 생길 때마다? -> 계속 읽어보니 readiness probe 가 성공했을 때 추가된다고 한다. Pod selector 가 YAML 파일에 정의되어 있기는 하지만, 실제로 service 가 요청을 처리할 때는 pod selector 를 사용하지 않고 이 endpoints 에 있는 값들을 사용한다. Selector 는 endpoints 의 값들을 만들 때 사용한다.
### 5.2.2 Service endpoints 직접 설정하기 그럼 endpoints 의 값들은 언제 갱신하는데? Pod 이 생길 때마다? -> 계속 읽어보니 readiness probe 가 성공했을 때 추가된다고 한다.
Pod selector 가 없는 service 를 만들면 endpoints 설정되지 않는다. ### 5.2.2 Service endpoints 직접 설정하기
Endpoints 리소스를 직접 만들어서 service 에 갖다 붙일 수 있다. 다만 만들어진 endpoints 리소스의 이름은 service 의 이름과 동일해야 한다. Pod selector 가 없는 service 를 만들 endpoints 가 설정되지 않는다.
이 방법을 이용하면 외부의 서버 등에 연결할 수 있는 service 를 하나 만들어두고 pod 들이 외부 서버에 접속할 때 해당 service 로 요청을 보내도록 할 수 있다. (로드 밸런싱은 덤) Endpoints 리소스를 직접 만들어서 service 에 갖다 붙일 수 있다. 다만 만들어진 endpoints 리소스의 이름은 service 의 이름과 동일해야 한다.
Label selector 를 지정하면 endpoints 가 자동으로 관리되고, label selector 를 제거하면 endpoints 의 갱신은 멈춘다. 이 방법을 이용하면 외부의 서버 등에 연결할 수 있는 service 를 하나 만들어두고 pod 들이 외부 서버에 접속할 때 해당 service 로 요청을 보내도록 할 수 있다. (로드 밸런싱은 덤)
### 5.2.3 외부의 서비스 alias 설정 Label selector 를 지정하면 endpoints 가 자동으로 관리되고, label selector 를 제거하면 endpoints 의 갱신은 멈춘다.
외부 서비스의 FQDN 을 이용해서 접근할 수 있다. Service 오브젝트를 생성할 때 `.spec.type` 를 `ExternalName` 으로 설정하면 된다. 이렇게 하면 외부 서비스의 FQDN 을 직접 이용하지 않고 service 를 경유하여 사용할 수 있게 된다. 나중에 외부 서비스를 교체할 일이 생길 때 훨씬 수월하게 교체할 수 있게 된다. ### 5.2.3 외부 서비스 alias 설정
`ExternalName` service 들은 DNS 레벨에서 구현되어 있어서 `CNAME` record 가 생성된다. 이 service 에 접속을 시도하는 client 들은 외부 서비스에 직접 접속하며, service 에는 IP 가 할당 되지 않는다. 외부 서비스의 FQDN 을 이용해서 접근할 수 있다. Service 오브젝트를 생성할 때 `.spec.type``ExternalName` 으로 설정하면 된다. 이렇게 하면 외부 서비스의 FQDN 을 직접 이용하지 않고 service 를 경유하여 사용할 수 있게 된다. 나중에 외부 서비스를 교체할 일이 생길 때 훨씬 수월하게 교체할 수 있게 된다.
## 5.3 Service 를 외부에 공개하기 `ExternalName` service 들은 DNS 레벨에서 구현되어 있어서 `CNAME` record 가 생성된다. 이 service 에 접속을 시도하는 client 들은 외부 서비스에 직접 접속하며, service 에는 IP 가 할당 되지 않는다.
프론트엔드의 경우 외부로 공개할 필요가 있다. 공개하는 방법에는 세 가지 방법이 있다. ## 5.3 Service 를 외부에 공개하기
---
- Service 타입 `NodePort` 로 설정하기: 클러스터의 *각* 노드가 한 포트를 개방한다. 개방된 포트로 들어오는 요청은 해당 service 로 redirect 된다. 특정 노드에 구애받지 않고, 모든 노드의 해당 포트로 들어오는 요청이 redirect 된다.
- Service 타입 `LoadBalancer` 로 설정하기: 해당 service 를 위한 load balancer 의 IP 주소를 통해서 service 에 접근할 수 있도록 한다. (Cloud infrastructure 에서 관리) 프론트엔드의 경우 외부로 공개할 필요가 있다. 공개하는 방법에는 세 가지 방법이 있다.
- Ingress 리소스 만들기: HTTP level 에서 동작한다. 5.4 에서 설명한다.
- Service 타입 `NodePort` 로 설정하기: 클러스터의 *각* 노드가 한 포트를 개방한다. 개방된 포트로 들어오는 요청은 해당 service 로 redirect 된다. 특정 노드에 구애받지 않고, 모든 노드의 해당 포트로 들어오는 요청이 redirect 된다.
### 5.3.1 NodePort service - Service 타입 `LoadBalancer` 로 설정하기: 해당 service 를 위한 load balancer 의 IP 주소를 통해서 service 에 접근할 수 있도록 한다. (Cloud infrastructure 에서 관리)
- Ingress 리소스 만들기: HTTP level 에서 동작한다. 5.4 에서 설명한다.
`NodePort` service 를 생성하면 모든 노드의 한 포트가 reserve 된다. 해당 포트로 들어오는 요청은 모두 service 로 연결된다.
### 5.3.1 NodePort service
일반적인 service 와 크게 다르지 않지만, cluster IP 뿐만 아니라 임의의 노드 IP 와 포트를 통해서도 접근이 가능하다.
`NodePort` service 를 생성하면 모든 노드의 한 포트가 reserve 된다. 해당 포트로 들어오는 요청은 모두 service 로 연결된다.
#### NodePort service 생성
일반적인 service 와 크게 다르지 않지만, cluster IP 뿐만 아니라 임의의 노드 IP 와 포트를 통해서도 접근이 가능하다.
```yaml
... #### NodePort service 생성
spec:
type: NodePort ```yaml
ports: ...
- port: 80 spec:
targetPort: 8080 type: NodePort
nodePort: 30123 # 노드의 포트를 개방하도록 설정 ports:
``` - port: 80
targetPort: 8080
개방할 포트를 안적으면 random 하게 선택되며, GKE 의 경우 방화벽 설정을 해야한다. nodePort: 30123 # 노드의 포트를 개방하도록 설정
```
모든 노드의 IP 주소로부터 접근이 가능하지만, 만약 어떤 client 가 무조건 한 노드의 IP 로만 접근하는데 해당 노드가 죽는 경우에는 접근이 불가능해진다. 따라서 load balancer 를 사용하는 방법이 더 바람직하다.
개방할 포트를 안적으면 random 하게 선택되며, GKE 의 경우 방화벽 설정을 해야한다.
### 5.3.2 Exposing a service through an external load balancer
모든 노드의 IP 주소로부터 접근이 가능하지만, 만약 어떤 client 가 무조건 한 노드의 IP 로만 접근하는데 해당 노드가 죽는 경우에는 접근이 불가능해진다. 따라서 load balancer 를 사용하는 방법이 더 바람직하다.
Service 타입을 `LoadBalancer` 로 설정하면 된다. 다만 cloud infrastructure 단에서 load balancer 를 지원해줘야 한다. 만약 지원해주지 않는다면 `NodePort` service 와 동일하게 동작한다. (extension 이기 때문)
### 5.3.2 Exposing a service through an external load balancer
`LoadBalancer` service 를 생성하게 되면 해당 service 에 external IP 가 설정되며, 해당 주소로 접근할 수 있다.
Service 타입을 `LoadBalancer` 로 설정하면 된다. 다만 cloud infrastructure 단에서 load balancer 를 지원해줘야 한다. 만약 지원해주지 않는다면 `NodePort` service 와 동일하게 동작한다. (extension 이기 때문)
`LoadBalancer` service 를 생성할 때도 `NodePort` service 가 그랬듯이 모든 노드에서 한 포트를 개방하게 되며, 로드 밸런싱을 거쳐 적절한 노드로 요청을 redirect 해준다. 또한 노드에서 한 포트가 개방되는 셈이므로, `kubectl describe` 를 이용하여 개방된 포트를 확인하고 방화벽 설정을 고쳐주면 `NodePort` service 처럼 노드의 IP 주소와 포트를 이용해 접근할 수 있다.
`LoadBalancer` service 를 생성하게 되면 해당 service 에 external IP 가 설정되며, 해당 주소로 접근할 수 있다.
### 5.3.3 Understanding the peculiarities of external connections
`LoadBalancer` service 를 생성할 때도 `NodePort` service 가 그랬듯이 모든 노드에서 한 포트를 개방하게 되며, 로드 밸런싱을 거쳐 적절한 노드로 요청을 redirect 해준다. 또한 노드에서 한 포트가 개방되는 셈이므로, `kubectl describe` 를 이용하여 개방된 포트를 확인하고 방화벽 설정을 고쳐주면 `NodePort` service 처럼 노드의 IP 주소와 포트를 이용해 접근할 수 있다.
#### Avoiding additional network hops
### 5.3.3 Understanding the peculiarities of external connections
`NodePort` service 를 이용하게 되면 요청을 받은 노드에 요청을 처리해줄 pod 이 존재하지 않을 수도 있다. 혹은 임의로 선택된 pod 가 해당 노드에 존재하지 않을 수도 있다. 이 때 해당 pod 이 존재하는 노드로 가야하기 때문에 추가 delay 가 생긴다.
#### Avoiding additional network hops
`.spec.externalTrafficPolicy` 를 `local` 로 설정하면 요청을 받은 노드 안에 있는 pod 로만 redirect 된다. 만약에 그런 pod 가 없으면 대기한다. 그러므로 쓰기 전에 노드에 pod 가 있음을 확신할 수 있어야 한다.
`NodePort` service 를 이용하게 되면 요청을 받은 노드에 요청을 처리해줄 pod 이 존재하지 않을 수도 있다. 혹은 임의로 선택된 pod 가 해당 노드에 존재하지 않을 수도 있다. 이 때 해당 pod 이 존재하는 노드로 가야하기 때문에 추가 delay 가 생긴다.
위 옵션을 사용하면 더 이상 요청이 pod 별로 균등하게 분배되지 않는다.
`.spec.externalTrafficPolicy``local` 로 설정하면 요청을 받은 노드 안에 있는 pod 로만 redirect 된다. 만약에 그런 pod 가 없으면 대기한다. 그러므로 쓰기 전에 노드에 pod 가 있음을 확신할 수 있어야 한다.
#### Non-preservation of the client's IP
위 옵션을 사용하면 더 이상 요청이 pod 별로 균등하게 분배되지 않는다.
`NodePort` service 를 통해 외부에서 접근하게 되면 SNAT 로 인해 source IP 가 변경된다. (내부에서는 무관) 그런데 만약 애플리케이션이 source IP 를 필요로 한다면 문제가 될 수도 있다.
#### Non-preservation of the client's IP
## 5.4 Ingress resource
`NodePort` service 를 통해 외부에서 접근하게 되면 SNAT 로 인해 source IP 가 변경된다. (내부에서는 무관) 그런데 만약 애플리케이션이 source IP 를 필요로 한다면 문제가 될 수도 있다.
### Ingress 가 필요한 이유
## 5.4 Ingress resource
`LoadBalancer` service 는 각 service 마다 load balancer 를 생성하고, 공개 IP 주소가 필요하지만, Ingress 는 여러 개의 service 를 관리하는데도 하나의 IP 주소만 있어도 된다. HTTP 요청을 받을 때, host 와 path 를 보고 어느 service 로 redirect 해야하는지 알 수 있다. ---
또한 application layer 에서 동작하기 때문에 service 가 제공하지 않는 기능들을 사용할 수 있다. ### Ingress 가 필요한 이유
(IP 주소 하나 쓰는 것만으로는 조금 부족해 보이는데 어떤 기능들이 있는지 확인해 봐야겠다.) `LoadBalancer` service 는 각 service 마다 load balancer 를 생성하고, 공개 IP 주소가 필요하지만, Ingress 는 여러 개의 service 를 관리하는데도 하나의 IP 주소만 있어도 된다. HTTP 요청을 받을 때, host 와 path 를 보고 어느 service 로 redirect 해야하는지 알 수 있다.
더불어 Ingress 가 동작하려면 Ingress controller 가 동작하고 있어야 한다. 또한 application layer 에서 동작하기 때문에 service 가 제공하지 않는 기능들을 사용할 수 있다.
### 5.4.1 Ingress 리소스 생성 (IP 주소 하나 쓰는 것만으로는 조금 부족해 보이는데 어떤 기능들이 있는지 확인해 봐야겠다.)
```yaml 더불어 Ingress 가 동작하려면 Ingress controller 가 동작하고 있어야 한다.
apiVersion: extensions/v1beta1
kind: Ingress ### 5.4.1 Ingress 리소스 생성
metadata:
name: sung ```yaml
spec: apiVersion: extensions/v1beta1
rules: kind: Ingress
- host: sung.example.com # 이 주소로 들어오는 요청은 서비스로 redirect metadata:
http: name: sung
paths: spec:
- path: / rules:
backend: - host: sung.example.com # 이 주소로 들어오는 요청은 서비스로 redirect
serviceName: sung http:
servicePort: 80 paths:
``` - path: /
backend:
Note. 위 처럼 생성하면 warning 이 발생한다. serviceName: sung
servicePort: 80
``` ```
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
``` Note. 위 처럼 생성하면 warning 이 발생한다.
### 5.4.2 Ingress 를 이용하여 service 접속 ```
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
Domain name resolution 이 잘 되어야 한다! `kubectl get ingresses` 로 IP 를 확인할 수 있다. Ingress 에 입력한 `host` 값이 해당 IP 로 resolve 될 수 있도록 직접 설정해줘야 한다. `/etc/hosts` 를 쓰거나, DNS 서버를 직접 사용하거나... ```
#### How does it work? ### 5.4.2 Ingress 를 이용하여 service 접속
1. Client 가 `sung.example.com` 의 IP 주소를 DNS 에서 찾아본다. Domain name resolution 이 잘 되어야 한다! `kubectl get ingresses` 로 IP 를 확인할 수 있다. Ingress 에 입력한 `host` 값이 해당 IP 로 resolve 될 수 있도록 직접 설정해줘야 한다. `/etc/hosts` 를 쓰거나, DNS 서버를 직접 사용하거나...
2. 받은 IP 주소에 HTTP 요청을 보낼 때 `Host: sung.example.com` 헤더를 넣어서 보낸다.
3. Ingress controller 는 어느 service 에 대한 요청인지 파악한다. #### How does it work?
4. 해당 서비스의 endpoints 중 하나의 주소로 요청을 보낸다.
1. Client 가 `sung.example.com` 의 IP 주소를 DNS 에서 찾아본다.
유의하여 볼 점은 service 에게 요청을 전달하지 않고 직접 pod 를 선택했다는 점이다. 2. 받은 IP 주소에 HTTP 요청을 보낼 때 `Host: sung.example.com` 헤더를 넣어서 보낸다.
3. Ingress controller 는 어느 service 에 대한 요청인지 파악한다.
### 5.4.3 하나의 Ingress 에 여러 service 연결하기 4. 해당 서비스의 endpoints 중 하나의 주소로 요청을 보낸다.
#### Mulitple paths 유의하여 볼 점은 service 에게 요청을 전달하지 않고 직접 pod 를 선택했다는 점이다.
```yaml ### 5.4.3 하나의 Ingress 에 여러 service 연결하기
...
- host: sung.example.com #### Mulitple paths
http:
paths: ```yaml
- path: /sung # sung.example.com/sung ...
backend: - host: sung.example.com
serviceName: sung http:
servicePort: 80 paths:
- path: /foo # sung.example.com/foo - path: /sung # sung.example.com/sung
backend: backend:
serviceName: bar serviceName: sung
servicePort: 80 servicePort: 80
``` - path: /foo # sung.example.com/foo
backend:
각 path 별로 다른 service 를 사용하도록 설정할 수 있다. serviceName: bar
servicePort: 80
#### Multiple hosts ```
`host` 를 여러 개 등록하면 `Host: ...` 헤더 값에 따라 다른 service 에 접근할 수 있다. DNS 에 `host` 값을 등록하는 것을 잊으면 안 된다. 각 path 별로 다른 service 를 사용하도록 설정할 수 있다.
### 5.4.4 Configuring Ingress to handle TLS traffic #### Multiple hosts
HTTPS 요청을 처리하는 방법에 대해서 알아본다. `host` 를 여러 개 등록하면 `Host: ...` 헤더 값에 따라 다른 service 에 접근할 수 있다. DNS 에 `host` 값을 등록하는 것을 잊으면 안 된다.
#### TLS certificate for the Ingress ### 5.4.4 Configuring Ingress to handle TLS traffic
Client 와 controller 사이의 통신은 암호화 되어있지만, controller 와 pod 사이의 통신은 암호화 되어있지 않다. 그러므로 pod 에서 돌아가는 앱은 TLS 를 지원할 필요는 없다. HTTPS 요청을 처리하는 방법에 대해서 알아본다.
인증서와 private key 를 생성하여 Ingress 에 넣어주어야 한다. 원래는 Secret 이라는 쿠버네티스 리소스에 저장되지만 7장에서 자세히 살펴본다. #### TLS certificate for the Ingress
```bash Client 와 controller 사이의 통신은 암호화 되어있지만, controller 와 pod 사이의 통신은 암호화 되어있지 않다. 그러므로 pod 에서 돌아가는 앱은 TLS 를 지원할 필요는 없다.
$ openssl genrsa -out tls.key 2048
$ openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj CN=sung.example.com 인증서와 private key 를 생성하여 Ingress 에 넣어주어야 한다. 원래는 Secret 이라는 쿠버네티스 리소스에 저장되지만 7장에서 자세히 살펴본다.
# Create Secret ```bash
$ kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key $ openssl genrsa -out tls.key 2048
``` $ openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj CN=sung.example.com
이제 YAML 파일을 수정한다. # Create Secret
$ kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
```yaml ```
...
spec: 이제 YAML 파일을 수정한다.
tls:
- hosts: ```yaml
- sung.example.com ...
secretName: tls-secret spec:
rules: tls:
... - hosts:
``` - sung.example.com
secretName: tls-secret
## 5.5 연결을 받을 준비가 되었을 때 신호 보내기 rules:
...
Service 나 Ingress 를 사용하면 pod 가 생성되자마자 요청이 전달될 수 있다. (label 만 검사하므로) 한편 startup 이 오래걸리는 애플리케이션의 경우 아직 pod 는 준비가 안되었는데 요청을 받게 되어 요청을 정상적으로 처리 못하게 될 수 있다. ```
Pod 가 요청을 처리할 준비가 되었확인할 방법이 필요하다. ## 5.5 연결을 받을 준비가 되었을 때 신호 보내기
---
### 5.5.1 Readiness probes
Service 나 Ingress 를 사용하면 pod 가 생성되자마자 요청이 전달될 수 있다. (label 만 검사하므로) 한편 startup 이 오래걸리는 애플리케이션의 경우 아직 pod 는 준비가 안되었는데 요청을 받게 되어 요청을 정상적으로 처리 못하게 될 수 있다.
Readiness probe 는 주기적으로 실행되며, pod 가 client request 를 받을 준비가 되었는지 확인한다.
Pod 가 요청을 처리할 준비가 되었음을 확인할 방법이 필요하다.
종류는 liveness probe 와 마찬가지로 세 종류가 있다.
### 5.5.1 Readiness probes
- **HTTP GET probe**: 컨테이너 IP 주소에 GET 요청 보내보기
- 응답 코드가 2xx, 3xx 이면 OK Readiness probe 는 주기적으로 실행되며, pod 가 client request 를 받을 준비가 되었는지 확인한다.
- 이외의 에러 코드나 응답이 없으면 실패로 간주
- **TCP socket probe**: 컨테이너의 정해진 포트에 TCP 연결을 시도해서 성공하면 준비된 것으로 판단 종류는 liveness probe 와 마찬가지로 세 종류가 있다.
- **Exec probe**: 컨테이너 안에서 임의의 명령을 실행하고 exit code 가 0 이면 준비된 것으로 판단
- **HTTP GET probe**: 컨테이너 IP 주소에 GET 요청 보내보기
#### Understanding the operation of readiness probes - 응답 코드가 2xx, 3xx 이면 OK
- 이외의 에러 코드나 응답이 없으면 실패로 간주
- 첫 readiness probe 를 실행하기 전 얼만큼 기다릴지 미리 설정할 수 있다. 그 이후에는 주기적으로 실행한다. - **TCP socket probe**: 컨테이너의 정해진 포트에 TCP 연결을 시도해서 성공하면 준비된 것으로 판단
- Ready 상태가 아니면 service 의 endpoints 에서 제거되고, 준비가 되면 추가된다. - **Exec probe**: 컨테이너 안에서 임의의 명령을 실행하고 exit code 가 0 이면 준비된 것으로 판단
- Liveness probe 는 실패하면 컨테이너를 재시작하지만, readiness probe 는 그렇지 않다.
#### Understanding the operation of readiness probes
Pod 이 죽을 때 어떤 방식으로 endpoints 를 업데이트 하는지? -> 이게 아니고 readiness probe 가 실패해서 endpoints 가 업데이트 되는 듯 하다. + 삭제시 쿠버네티스가 알아서 업데이트 해준다.
- 첫 readiness probe 를 실행하기 전 얼만큼 기다릴지 미리 설정할 수 있다. 그 이후에는 주기적으로 실행한다.
아무튼, readiness probe 는 client 들이 healthy pod 에만 요청을 할 수 있도록 관리해준다. - Ready 상태가 아니면 service 의 endpoints 에서 제거되고, 준비가 되면 추가된다.
- Liveness probe 는 실패하면 컨테이너를 재시작하지만, readiness probe 는 그렇지 않다.
### 5.5.2 Pod 에 readiness probe 추가하기
Pod 이 죽을 때 어떤 방식으로 endpoints 를 업데이트 하는지? -> 이게 아니고 readiness probe 가 실패해서 endpoints 가 업데이트 되는 듯 하다. + 삭제시 쿠버네티스가 알아서 업데이트 해준다.
ReplicationController's pod template 를 수정하여 readiness probe 를 추가한다. 컨테이너별로 readiness probe 의 동작을 다르게 정할 수 있다.
아무튼, readiness probe 는 client 들이 healthy pod 에만 요청을 할 수 있도록 관리해준다.
ReplicationController 의 pod template 을 수정하면 현재 존재하는 pod 에는 영향이 없으므로, 존재하는 pod 들은 삭제하고 새롭게 pod 를 띄우거나, ready 상태가 될 수 있도록 적절한 동작을 취해줘야 할 수도 있다.
### 5.5.2 Pod 에 readiness probe 추가하기
### 5.5.3 Readiness probe 가 해야할 일
ReplicationController's pod template 를 수정하여 readiness probe 를 추가한다. 컨테이너별로 readiness probe 의 동작을 다르게 정할 수 있다.
- 항상 readiness probe 를 사용할 것
ReplicationController 의 pod template 을 수정하면 현재 존재하는 pod 에는 영향이 없으므로, 존재하는 pod 들은 삭제하고 새롭게 pod 를 띄우거나, ready 상태가 될 수 있도록 적절한 동작을 취해줘야 할 수도 있다.
사용하지 않으면, pod 의 시작과 동시에 service endpoints 에 추가되므로, 요청을 처리하지 못할 것이다.
### 5.5.3 Readiness probe 가 해야할 일
- Don't include pod shutdown logic
- 항상 readiness probe 를 사용할 것
Shutdown 이 시작되었음을 감지하고 readiness probe 가 실패하도록 로직을 짤 필요는 없다. Pod 가 삭제되면 쿠버네티스가 자동으로 service endpoints 에서 제거한다.
사용하지 않으면, pod 의 시작과 동시에 service endpoints 에 추가되므로, 요청을 처리하지 못할 것이다.
## 5.6 Headless service for discovering individual pods
- Don't include pod shutdown logic
Client 가 모든 pod 에 접근해야 한다면? (어떤 경우에 이러한가? - 아래 Discussion 참고)
Shutdown 이 시작되었음을 감지하고 readiness probe 가 실패하도록 로직을 짤 필요는 없다. Pod 가 삭제되면 쿠버네티스가 자동으로 service endpoints 에서 제거한다.
DNS lookup 을 이용할 수 있다! 보통 service 를 위해 DNS lookup 을 하면 하나의 IP (service IP) 를 돌려주지만, service 에 cluster IP 가 필요없다고 설정하면, endpoints 에 있는 IP 들을 DNS `A` 레코드로 돌려준다.
## 5.6 Headless service for discovering individual pods
### 5.6.1 Headless service 생성 ---
`.spec.clusterIP: None` 으로 설정하면 된다. Client 가 모든 pod 에 접근해야 한다면? (어떤 경우에 이러한가? - 아래 Discussion 참고)
### 5.6.2 Discovering pods through DNS DNS lookup 을 이용할 수 있다! 보통 service 를 위해 DNS lookup 을 하면 하나의 IP (service IP) 를 돌려주지만, service 에 cluster IP 가 필요없다고 설정하면, endpoints 에 있는 IP 들을 DNS `A` 레코드로 돌려준다.
`nslookup` 명령어로 확인하면 된다! ### 5.6.1 Headless service 생성
#### YAML 없이 pod 띄우기 `.spec.clusterIP: None` 으로 설정하면 된다.
```bash ### 5.6.2 Discovering pods through DNS
$ kubectl run <POD_NAME> --image=<IMAGE_NAME> --generator=run-pod/v1
``` `nslookup` 명령어로 확인하면 된다!
진짜로 pod 만 만들어준다. ReplicationController, service 이런거 없이 만들어 준다. #### YAML 없이 pod 띄우기
### 5.6.3 Discovering all pods - even those that aren't ready ```bash
$ kubectl run <POD_NAME> --image=<IMAGE_NAME> --generator=run-pod/v1
Service 에서 `publishNotReadyAddresses=True` 로 설정하면 된다. ```
## 5.7 Troubleshooting 진짜로 pod 만 만들어준다. ReplicationController, service 이런거 없이 만들어 준다.
순서대로 해보면 좋을 것이다! ### 5.6.3 Discovering all pods - even those that aren't ready
- Service cluster IP 를 사용하는 경우 클러스터 내에서 접속하고 있는지 확인하자. Service 에서 `publishNotReadyAddresses=True` 로 설정하면 된다.
- Service IP 에는 `ping` 이 동작하지 않는다.
- Readiness probe 를 설정했다면 성공하는지 꼭 확인해야 한다. 그렇지 않으면 pod 가 service 의 endpoints 에 등록되지 않는다. ## 5.7 Troubleshooting
- Pod 가 service 의 endpoints 에 등록되었는지 확인하려면 `kubectl get endpoints`. ---
- FQDN 으로 접속이 안되면, cluster IP 를 사용해서 되는지 확인해본다.
- Service 의 타겟 포트가 아니고 expose 한 포트로 접속하고 있는지 확인한다. 순서대로 해보면 좋을 것이다!
- Pod IP, 포트로 직접 접속하여 요청을 받는지 확인한다.
- 마지막으로, 앱이 localhost 에 바인딩 하지 않았는지 확인한다. - Service cluster IP 를 사용하는 경우 클러스터 내에서 접속하고 있는지 확인하자.
- Service IP 에는 `ping` 이 동작하지 않는다.
11장에서 service 가 어떻게 구현되었는지 공부하면 디버깅이 더 쉬울 것이라고 한다 ... - Readiness probe 를 설정했다면 성공하는지 꼭 확인해야 한다. 그렇지 않으면 pod 가 service 의 endpoints 에 등록되지 않는다.
- Pod 가 service 의 endpoints 에 등록되었는지 확인하려면 `kubectl get endpoints`.
--- - FQDN 으로 접속이 안되면, cluster IP 를 사용해서 되는지 확인해본다.
- Service 의 타겟 포트가 아니고 expose 한 포트로 접속하고 있는지 확인한다.
## Discussion & Additional Topics - Pod IP, 포트로 직접 접속하여 요청을 받는지 확인한다.
- 마지막으로, 앱이 localhost 에 바인딩 하지 않았는지 확인한다.
### When would you want to set sessionAffinity to `ClientIP` ?
11장에서 service 가 어떻게 구현되었는지 공부하면 디버깅이 더 쉬울 것이라고 한다 ...
- 좋은 방법은 아니겠지만, session 정보를 저장해야 하는 경우가 그럴 것이다. 예를 들면 pod A, B 가 실행 중인데 처음에 A 로 접속해서 로그인했다고 하자. 그러면 다음 요청을 보낼 때 A 로 가게 되면 로그인 된 것으로 처리하지만, pod B 로 요청이 가게 되면 로그인하지 않은 상태일 것이다.
- 위와 같은 경우 로그인 상태를 캐싱하고 있는 서버를 별도로 두는 것이 좋다. Pod B 에서 캐싱 서버로 요청을 보내 사용자의 로그인 여부를 판단할 수 있게 될 것이다. ---
### Current Ingress resource example ## Discussion & Additional Topics
- https://kubernetes.io/docs/concepts/services-networking/ingress/#the-ingress-resource ### When would you want to set sessionAffinity to `ClientIP` ?
### Why does Ingress controller select pods directly, and not pass the request to the service? - 좋은 방법은 아니겠지만, session 정보를 저장해야 하는 경우가 그럴 것이다. 예를 들면 pod A, B 가 실행 중인데 처음에 A 로 접속해서 로그인했다고 하자. 그러면 다음 요청을 보낼 때 A 로 가게 되면 로그인 된 것으로 처리하지만, pod B 로 요청이 가게 되면 로그인하지 않은 상태일 것이다.
- 위와 같은 경우 로그인 상태를 캐싱하고 있는 서버를 별도로 두는 것이 좋다. Pod B 에서 캐싱 서버로 요청을 보내 사용자의 로그인 여부를 판단할 수 있게 될 것이다.
### Pod 가 이미 에러나서 요청을 처리하지 못하지만 죽지는 않은 상태이다. 한편 readiness probe 가 아직 실행되지 않았으면 그 사이에 도착한 요청들은 다 망한건가? ㅋㅋ
### Current Ingress resource example
- 예.
- https://kubernetes.io/docs/concepts/services-networking/ingress/#the-ingress-resource
### 왜 pod 의 YAML 에서 설정하는 것이 아니고 rc 에서 하는 것일까? 현실에서는 어차피 복제될 pod 라 괜찮겠지만...
### Why does Ingress controller select pods directly, and not pass the request to the service?
- Pod 를 실제로 복제할 때 readiness probe 에 대한 정보도 필요하므로 rc 의 template 에 넣어야 맞을 것이다.
### Pod 가 이미 에러나서 요청을 처리하지 못하지만 죽지는 않은 상태이다. 한편 readiness probe 가 아직 실행되지 않았으면 그 사이에 도착한 요청들은 다 망한건가? ㅋㅋ
### When would we actually use `publishNotReadyAddresses=True` ?
- 예.
- https://stackoverflow.com/questions/51098498/keep-a-kubernetes-pod-in-service-when-in-a-state-of-not-ready
- 뭔지 잘 모르겠다. ### 왜 pod 의 YAML 에서 설정하는 것이 아니고 rc 에서 하는 것일까? 현실에서는 어차피 복제될 pod 라 괜찮겠지만...
### FQDN 사용 시 `cluster.local` 이 아닌 곳으로도 접근이 가능한 경우가 있을까? - Pod 를 실제로 복제할 때 readiness probe 에 대한 정보도 필요하므로 rc 의 template 에 넣어야 맞을 것이다.
### FQDN ### When would we actually use `publishNotReadyAddresses=True` ?
- https://en.wikipedia.org/wiki/Fully_qualified_domain_name - https://stackoverflow.com/questions/51098498/keep-a-kubernetes-pod-in-service-when-in-a-state-of-not-ready
- 뭔지 잘 모르겠다.
### DNS Records
### FQDN 사용 시 `cluster.local` 이 아닌 곳으로도 접근이 가능한 경우가 있을까?
- https://en.wikipedia.org/wiki/List_of_DNS_record_types
- `dig` 명령어로 DNS record 조회 가능하다. ### FQDN
#### 대표적인 records - https://en.wikipedia.org/wiki/Fully_qualified_domain_name
- A record: 도메인에 IP 를 할당한다.
- CNAME record: Canonical NAME record 으로, 특정 도메인을 다른 도메인 이름으로 매핑한다. ### DNS Records
### Load Balancer 이야기 - https://en.wikipedia.org/wiki/List_of_DNS_record_types
- `dig` 명령어로 DNS record 조회 가능하다.
- https://d2.naver.com/helloworld/605418
#### 대표적인 records
### Client 가 여러 pod 에 동시에 연결하는 상황의 예시 - A record: 도메인에 IP 를 할당한다.
- CNAME record: Canonical NAME record 으로, 특정 도메인을 다른 도메인 이름으로 매핑한다.
- 동영상 스트리밍 할 때, 자연스럽게 화질을 전환하는 경우!
- 고화질, 중화질, 저화질 서버를 각각 pod 으로 만들어 둔 뒤, client 는 모든 pod 에 연결하고, 네트워크 상황에 따라 pod 을 바꿔가며 동영상을 스트리밍 하면 된다. ### Load Balancer 이야기
### Can a single K8s pod host 2 or more K8s services? - https://d2.naver.com/helloworld/605418
- https://stackoverflow.com/questions/46981184/kubernetes-can-a-single-k8s-pod-host-2-or-more-k8s-services/46984384#46984384 ### Client 가 여러 pod 에 동시에 연결하는 상황의 예시
### JSONPath / XPath - 동영상 스트리밍 할 때, 자연스럽게 화질을 전환하는 경우!
- 고화질, 중화질, 저화질 서버를 각각 pod 으로 만들어 둔 뒤, client 는 모든 pod 에 연결하고, 네트워크 상황에 따라 pod 을 바꿔가며 동영상을 스트리밍 하면 된다.
- https://kubernetes.io/docs/reference/kubectl/jsonpath/
### Can a single K8s pod host 2 or more K8s services?
- https://stackoverflow.com/questions/46981184/kubernetes-can-a-single-k8s-pod-host-2-or-more-k8s-services/46984384#46984384
### JSONPath / XPath
- https://kubernetes.io/docs/reference/kubectl/jsonpath/

View File

@@ -1,372 +1,378 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops] tags: [kubernetes, sre, devops]
title: "06. Volumes: Attaching Disk Storage to Containers" title: "06. Volumes: Attaching Disk Storage to Containers"
date: "2021-04-07" date: "2021-04-07"
github_title: "2021-04-07-06-volumes" github_title: "2021-04-07-06-volumes"
image: image:
path: /assets/img/posts/k8s-06.jpeg path: /assets/img/posts/k8s-06.jpeg
--- ---
![k8s-06.jpeg](../../../assets/img/posts/k8s-06.jpeg) _The complete picture of dynamic provisioning of PersistentVolumes. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-6)_ ![k8s-06.jpeg](../../../assets/img/posts/k8s-06.jpeg) _The complete picture of dynamic provisioning of PersistentVolumes. (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-6)_
컨테이너가 재시작되면 기존 작업 내역이 모두 사라지게 될 수 있으므로, 컨테이너의 작업 내역을 저장하고 같은 pod 내의 다른 컨테이너가 함께 사용하는 저장 공간이다. 컨테이너가 재시작되면 기존 작업 내역이 모두 사라지게 될 수 있으므로, 컨테이너의 작업 내역을 저장하고 같은 pod 내의 다른 컨테이너가 함께 사용하는 저장 공간이다.
## 6.1 Introducing volumes ## 6.1 Introducing volumes
---
Pod 의 한 구성 부분으로 pod spec 에 정의되어 있다. 또 standalone Kubernetes object 가 아니라서 독립적으로 생성되거나 삭제될 수 없다.
Pod 의 한 구성 부분으로 pod spec 에 정의되어 있다. 또 standalone Kubernetes object 가 아니라서 독립적으로 생성되거나 삭제될 수 없다.
Pod 내의 모든 컨테이너로부터 접근이 가능하지만, 그렇게 하려면 mount 해줘야 한다.
Pod 내의 모든 컨테이너로부터 접근이 가능하지만, 그렇게 하려면 mount 해줘야 한다.
### 6.1.1 Volume 예시
### 6.1.1 Volume 예시
### 6.1.2 Available volume types
### 6.1.2 Available volume types
종류가 굉장히 많은데, 다 알지는 않아도 괜찮다고 한다.
종류가 굉장히 많은데, 다 알지는 않아도 괜찮다고 한다.
#### Volume types
#### Volume types
- `emptyDir`: 빈 폴더로 일시적인 데이터 저장에 사용
- `hostPath`: 노드의 파일 시스템에 있는 디렉토리를 사용할 때 - `emptyDir`: 빈 폴더로 일시적인 데이터 저장에 사용
- `gitRepo`: Git repository 를 checkout 하여 사용 - `hostPath`: 노드의 파일 시스템에 있는 디렉토리를 사용할 때
- `nfs`: NFS share - `gitRepo`: Git repository 를 checkout 하여 사용
- `gcePersistentDisk`, `awsElasticBlockStore`, `azureDisk`: 클라우드가 제공하는 스토리지 - `nfs`: NFS share
- `configMap`, `secret` `downwardAPI`: 리소스나 클러스터 정보를 pod 에게 알려주기 위해 사용하는 volume - `gcePersistentDisk`, `awsElasticBlockStore`, `azureDisk`: 클라우드가 제공하는 스토리지
- `persistentVolumeClaim`: pre- or dynamically provisioned persistent storage - `configMap`, `secret` `downwardAPI`: 리소스나 클러스터 정보를 pod 에게 알려주기 위해 사용하는 volume
- `persistentVolumeClaim`: pre- or dynamically provisioned persistent storage
## 6.2 Volume 을 이용한 컨테이너간 데이터 공유
## 6.2 Volume 을 이용한 컨테이너간 데이터 공유
### 6.2.1 `emptyDir` volume ---
`emptyDir` volume 을 사용하면 빈 디렉토리로 시작한다. Pod 안의 컨테이너는 해당 디렉토리에 자유롭게 쓸 (write) 수 있다. 대신 pod 이 지워지면 volume 도 함께 지워지므로, 썼던 내용은 모두 사라진다. ### 6.2.1 `emptyDir` volume
주로 컨테이너들 간에 파일을 공유할 때 유용한데, 한 컨테이너만 사용하는 경우에도 컨테이너가 일시적으로 디스크에 파일을 저장해야하는 경우에도 유용하게 사용할 수 있다. (sort operation on a large dataset that doesn't fit into memory) 더불어 어떤 경우에는 컨테이너의 파일시스템을 사용할 수 없는 (not writable) 경우도 있다. `emptyDir` volume 을 사용하면 빈 디렉토리로 시작한다. Pod 안의 컨테이너는 해당 디렉토리에 자유롭게 쓸 (write) 수 있다. 대신 pod 이 지워지면 volume 도 함께 지워지므로, 썼던 내용은 모두 사라진다.
#### 생성하기 주로 컨테이너들 간에 파일을 공유할 때 유용한데, 한 컨테이너만 사용하는 경우에도 컨테이너가 일시적으로 디스크에 파일을 저장해야하는 경우에도 유용하게 사용할 수 있다. (sort operation on a large dataset that doesn't fit into memory) 더불어 어떤 경우에는 컨테이너의 파일시스템을 사용할 수 없는 (not writable) 경우도 있다.
```yaml #### 생성하기
apiVersion: v1
kind: Pod ```yaml
metadata: apiVersion: v1
name: fortune kind: Pod
spec: metadata:
containers: name: fortune
- image: luksa/fortune spec:
name: html-generator containers:
volumeMounts: - image: luksa/fortune
- name: html name: html-generator
mountPath: /var/htdocs # 컨테이너 내의 /var/htdocs 에 mount volumeMounts:
- image: nginx:alpine - name: html
name: web-server mountPath: /var/htdocs # 컨테이너 내의 /var/htdocs 에 mount
volumeMounts: - image: nginx:alpine
- name: html name: web-server
mountPath: /usr/share/nginx/html # 컨테이너 내의 해당 디렉토리에 mount volumeMounts:
readOnly: true - name: html
ports: mountPath: /usr/share/nginx/html # 컨테이너 내의 해당 디렉토리에 mount
- containerPort: 80 readOnly: true
protocol: TCP ports:
volumes: - containerPort: 80
- name: html # 이 volume 을 두 컨테이너가 공유한다 protocol: TCP
emptyDir: {} volumes:
``` - name: html # 이 volume 을 두 컨테이너가 공유한다
emptyDir: {}
`html-generator` 에서 생성한 파일을 volume 에 쓰면 `web-server` 가 내용을 읽고 서빙해준다. ```
`emptyDir` 를 사용하면 실제 노드의 디스크에 volume 이 생성된다. 그래서 노드의 디스크 타입에 따라 성능이 달라질 수 있다. `.volumes.emptyDir` 에 `medium` 을 설정하게 되면 tmpfs 파일시스템 (메모리 위의 파일시스템) 을 사용할 수도 있다. `html-generator` 에서 생성한 파일을 volume 에 쓰면 `web-server` 가 내용을 읽고 서빙해준다.
```yaml `emptyDir` 를 사용하면 실제 노드의 디스크에 volume 이 생성된다. 그래서 노드의 디스크 타입에 따라 성능이 달라질 수 있다. `.volumes.emptyDir``medium` 을 설정하게 되면 tmpfs 파일시스템 (메모리 위의 파일시스템) 을 사용할 수도 있다.
volumes:
- name: html ```yaml
emptyDir: volumes:
medium: Memory - name: html
``` emptyDir:
medium: Memory
### 6.2.2 Git 레포를 사용하기 ```
`gitRepo` volume 은 `emptyDir` volume 인데 시작할 때 git repository 의 내용이 채워진다. (컨테이너가 생성되기 전에) ### 6.2.2 Git 레포를 사용하기
Git repository 가 업데이트 되더라도, volume 은 업데이트 되지 않는다. 단 pod 이 재시작 되는 등 volume 이 다시 생성되게 되면 repo 의 변경사항이 반영된다. 단점이 있다면, repo 에 push 해서 변경사항이 생길 때마다 restart 해야한다. `gitRepo` volume 은 `emptyDir` volume 인데 시작할 때 git repository 의 내용이 채워진다. (컨테이너가 생성되기 전에)
#### 생성하기 Git repository 가 업데이트 되더라도, volume 은 업데이트 되지 않는다. 단 pod 이 재시작 되는 등 volume 이 다시 생성되게 되면 repo 의 변경사항이 반영된다. 단점이 있다면, repo 에 push 해서 변경사항이 생길 때마다 restart 해야한다.
```yaml #### 생성하기
apiVersion: v1
kind: Pod ```yaml
metadata: apiVersion: v1
name: gitrepo-pod kind: Pod
spec: metadata:
containers: name: gitrepo-pod
- image: nginx:alpine spec:
name: web-server containers:
volumeMounts: - image: nginx:alpine
- name: html name: web-server
mountPath: /usr/share/nginx/html volumeMounts:
readOnly: true - name: html
ports: mountPath: /usr/share/nginx/html
- containerPort: 80 readOnly: true
protocol: TCP ports:
volumes: - containerPort: 80
- name: html protocol: TCP
gitRepo: volumes:
repository: https://github.com/luksa/kubia-website-example.git # 어떤 repo 를 사용할지 - name: html
revision: master # master branch 사용 gitRepo:
directory: . # volume 의 root 디렉토리에 clone repository: https://github.com/luksa/kubia-website-example.git # 어떤 repo 를 사용할지
``` revision: master # master branch 사용
directory: . # volume 의 root 디렉토리에 clone
#### 자동 업데이트 ```
Sidecar container 를 사용해서 자동으로 repo 와 동기화시킬 수 있다. #### 자동 업데이트
> Sidecar container: Container that augments the operation of the main container of the pod Sidecar container 를 사용해서 자동으로 repo 와 동기화시킬 수 있다.
Docker Hub 에서 `git sync` 를 검색하면 많은 이미지들이 나올 것이다. 해당 이미지를 YAML 에 추가해서 자동으로 sync 하도록 설정하고 pod 를 만들어주면 될 것이다. > Sidecar container: Container that augments the operation of the main container of the pod
#### Private repository Docker Hub 에서 `git sync` 를 검색하면 많은 이미지들이 나올 것이다. 해당 이미지를 YAML 에 추가해서 자동으로 sync 하도록 설정하고 pod 를 만들어주면 될 것이다.
현재 private repository 는 clone 이 안 된다. `gitRepo` 옵션을 간단하게 유지하고 싶다고 한다. #### Private repository
> Warning: The `gitRepo` volume type is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container. 현재 private repository 는 clone 이 안 된다. `gitRepo` 옵션을 간단하게 유지하고 싶다고 한다.
(어쩐지, 위 예시 코드 작동 안하더라.) > Warning: The `gitRepo` volume type is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container.
## 6.3 노드의 파일 시스템에 접근하기 (어쩐지, 위 예시 코드 작동 안하더라.)
보통 pod 들은 자신이 어떤 노드 위에서 돌아가고 있는지 알 필요가 없기 때문에 노드의 파일 시스템에 접근할 일이 없다. 하지만 시스템 레벨의 pod 들은 (DaemonSet 이라던가) 노드의 파일을 읽어야 하는 경우가 생길 수도 있다. 이런 경우에는 `hostPath` 볼륨을 사용한다. ## 6.3 노드의 파일 시스템에 접근하기
---
### 6.3.1 `hostPath` volume
보통 pod 들은 자신이 어떤 노드 위에서 돌아가고 있는지 알 필요가 없기 때문에 노드의 파일 시스템에 접근할 일이 없다. 하지만 시스템 레벨의 pod 들은 (DaemonSet 이라던가) 노드의 파일을 읽어야 하는 경우가 생길 수도 있다. 이런 경우에는 `hostPath` 볼륨을 사용한다.
노드의 파일 시스템에 있는 한 파일이나 디렉토리를 매핑해준다. 같은 노드에 있는 pod 들이 같은 경로의 `hostPath` volume 을 사용하고 있다면 같은 파일을 보게 된다.
### 6.3.1 `hostPath` volume
`hostPath` volume 은 앞서 살펴본 `emptyDir`, `gitRepo` 와는 다르게 pod 이 지워진다고 해서 삭제되지는 않는다. (컨테이너가 디렉토리에 뭔가 write 했다면 그것이 노드에 남아있는 듯 하다. 다른 pod 이 같은 노드로 스케쥴링 되고 같은 경로로 mount 하면 작업하고 남은 것들을 확인할 수 있는 듯)
노드의 파일 시스템에 있는 한 파일이나 디렉토리를 매핑해준다. 같은 노드에 있는 pod 들이 같은 경로의 `hostPath` volume 을 사용하고 있다면 같은 파일을 보게 된다.
그렇다고 해서 pod 들 간에 파일을 공유하는 목적으로 사용해서는 안 된다. 어느 노드에 스케쥴링 될지 알 수 없다.
`hostPath` volume 은 앞서 살펴본 `emptyDir`, `gitRepo` 와는 다르게 pod 이 지워진다고 해서 삭제되지는 않는다. (컨테이너가 디렉토리에 뭔가 write 했다면 그것이 노드에 남아있는 듯 하다. 다른 pod 이 같은 노드로 스케쥴링 되고 같은 경로로 mount 하면 작업하고 남은 것들을 확인할 수 있는 듯)
### 6.3.2 System pods that use `hostPath` volumes
그렇다고 해서 pod 들 간에 파일을 공유하는 목적으로 사용해서는 안 된다. 어느 노드에 스케쥴링 될지 알 수 없다.
`kube-system` namespace 의 몇 pod 들에 대해 `kubectl describe` 를 해보면 `hostPath` volume 을 사용중인 것을 알 수 있다. 다른 pod 도 사용하고 있을 수도 있는데, 주로 자신의 데이터를 디렉토리에 쓰기보다는 노드의 정보를 가져와서 사용하기 위해 `hostPath` volume 을 사용한다.
### 6.3.2 System pods that use `hostPath` volumes
Single-node 클러스터에서는 `hostPath` volume 을 persistent storage 로 사용할 수 있다. (Minikube) 아무튼 multi-node 클러스터에서는 여러 pod 들 간에 데이터를 공유하거나 persistent storage 로 사용해서는 안된다.
`kube-system` namespace 의 몇 pod 들에 대해 `kubectl describe` 를 해보면 `hostPath` volume 을 사용중인 것을 알 수 있다. 다른 pod 도 사용하고 있을 수도 있는데, 주로 자신의 데이터를 디렉토리에 쓰기보다는 노드의 정보를 가져와서 사용하기 위해 `hostPath` volume 을 사용한다.
## 6.4 Persistent storage
Single-node 클러스터에서는 `hostPath` volume 을 persistent storage 로 사용할 수 있다. (Minikube) 아무튼 multi-node 클러스터에서는 여러 pod 들 간에 데이터를 공유하거나 persistent storage 로 사용해서는 안된다.
Pod 들 간에 데이터를 공유해야 한다면, 위에서 언급한 volume type 들은 사용할 수 없다. 클러스터 내의 임의의 노드에서 접근이 가능해야하기 때문에 NAS (network attached storage) 급의 저장장치가 필요하다.
## 6.4 Persistent storage
### 6.4.1 GCE Persistent Disk in a pod volume ---
생략. Pod 들 간에 데이터를 공유해야 한다면, 위에서 언급한 volume type 들은 사용할 수 없다. 클러스터 내의 임의의 노드에서 접근이 가능해야하기 때문에 NAS (network attached storage) 급의 저장장치가 필요하다.
GCE Persistent Disk 를 생성하고, 이를 mount 한 pod 에서 DB 에 뭔가 write 한 뒤, pod 를 삭제하고 재생성하여 find 해보면 존재한다는 내용이다. ### 6.4.1 GCE Persistent Disk in a pod volume
### 6.4.2 Other types of volumes with underlying persistent storage 생략.
- AWS: `awsElasticBlockStore` GCE Persistent Disk 를 생성하고, 이를 mount 한 pod 에서 DB 에 뭔가 write 한 뒤, pod 를 삭제하고 재생성하여 find 해보면 존재한다는 내용이다.
- Azure: `azureFile`, `azureDisk`
### 6.4.2 Other types of volumes with underlying persistent storage
굉장히 다양한 종류의 volume type 을 지원하긴 하는데, 이걸 모두 알 필요는 당연히 없다. 또한 infrastructure 와 관련된 저장소 타입에 대한 정보를 개발자가 알아야할 이유도 없다. Kubernetes 는 이런 정보를 추상화시키는 것이 목적이다. 특정 저장소 정보를 YAML 파일에 기록하게 되면 해당 클러스터의 하드웨어 인프라와 지나치게 연결되어, 다른 클러스터에서 돌아가지 않게 된다. 위에서 제시한 persistent volume 들은 하나의 방법이긴 하지만 최선은 아니다.
- AWS: `awsElasticBlockStore`
- Azure: `azureFile`, `azureDisk`
## 6.5 스토리지 기술과 pod 를 분리하기
굉장히 다양한 종류의 volume type 을 지원하긴 하는데, 이걸 모두 알 필요는 당연히 없다. 또한 infrastructure 와 관련된 저장소 타입에 대한 정보를 개발자가 알아야할 이유도 없다. Kubernetes 는 이런 정보를 추상화시키는 것이 목적이다. 특정 저장소 정보를 YAML 파일에 기록하게 되면 해당 클러스터의 하드웨어 인프라와 지나치게 연결되어, 다른 클러스터에서 돌아가지 않게 된다. 위에서 제시한 persistent volume 들은 하나의 방법이긴 하지만 최선은 아니다.
개발자는 물리적 저장소에 대한 정보를 몰라야 한다! 그것은 클러스터 관리자가 할 일이다.
### 6.5.1 PersistentVolumes and PersistentVolumeClaims ## 6.5 스토리지 기술과 pod 를 분리하기
---
개발자는 그저 저장소를 달라고 요청을 하고, 요청을 받았을 때 어떤 저장소를 줄지는 클러스터 관리자가 설정한다.
개발자는 물리적 저장소에 대한 정보를 몰라야 한다! 그것은 클러스터 관리자가 할 일이다.
이를 위해 PersistentVolume (PV) 과 PersistentVolumeClaim (PVC) 이라는 리소스가 생겨났다.
### 6.5.1 PersistentVolumes and PersistentVolumeClaims
개발자가 직접 저장소를 pod 에 mount 하는 것이 아니라, 클러스터 관리자가 실제 저장소를 만들어두고 쿠버네티스에 PersistentVolume 이라는 리소스로 등록해 두는 것이다. (이때 저장소의 크기와 권한을 설정할 수 있다)
개발자는 그저 저장소를 달라고 요청을 하고, 요청을 받았을 때 어떤 저장소를 줄지는 클러스터 관리자가 설정한다.
이제 클러스터의 사용자가 저장소가 필요하다면, PVC manifest 를 만들어서 최소 용량과 필요한 권한을 명시한다. 이 정보를 이용하여 쿠버네티스 API 에 요청하면 해당 정보에 맞는 PV 를 찾아 PVC 를 연결시켜준다. 이제 PVC 는 마치 pod 안에 mount 된 volume 처럼 사용할 수 있다. 그리고 PVC 가 지워지기 전까지 연결된 해당 PV 는 다른 PVC 가 사용할 수 없다.
이를 위해 PersistentVolume (PV) 과 PersistentVolumeClaim (PVC) 이라는 리소스가 생겨났다.
### 6.5.2 PersistentVolume 생성
개발자가 직접 저장소를 pod 에 mount 하는 것이 아니라, 클러스터 관리자가 실제 저장소를 만들어두고 쿠버네티스에 PersistentVolume 이라는 리소스로 등록해 두는 것이다. (이때 저장소의 크기와 권한을 설정할 수 있다)
```yaml
apiVersion: v1 이제 클러스터의 사용자가 저장소가 필요하다면, PVC manifest 를 만들어서 최소 용량과 필요한 권한을 명시한다. 이 정보를 이용하여 쿠버네티스 API 에 요청하면 해당 정보에 맞는 PV 를 찾아 PVC 를 연결시켜준다. 이제 PVC 는 마치 pod 안에 mount 된 volume 처럼 사용할 수 있다. 그리고 PVC 가 지워지기 전까지 연결된 해당 PV 는 다른 PVC 가 사용할 수 없다.
kind: PersistentVolume
metadata: ### 6.5.2 PersistentVolume 생성
name: mongodb-pv
spec: ```yaml
capacity: # 크기 apiVersion: v1
storage: 1Gi kind: PersistentVolume
accessModes: metadata:
- ReadWriteOnce # 하나의 PVC 만 읽거나 쓴다 name: mongodb-pv
- ReadOnlyMany # 여러 PVC 가 읽기만 한다 spec:
persistentVolumeReclaimPolicy: Retain # PVC 와 연결이 해제되면 지우거나 삭제하지 않고 유지한다 capacity: # 크기
hostPath: storage: 1Gi
path: /tmp/mongodb accessModes:
``` - ReadWriteOnce # 하나의 PVC 만 읽거나 쓴다
- ReadOnlyMany # 여러 PVC 가 읽기만 한다
크기와, 접근 방식, 그리고 reclaim 될 때의 동작, 그리고 실제 물리적인 저장소 정보 (위 예시에서는 `hostPath`) 를 명시해줘야 한다. persistentVolumeReclaimPolicy: Retain # PVC 와 연결이 해제되면 지우거나 삭제하지 않고 유지한다
hostPath:
참고로 PV 는 namespace 를 갖지 않는다. Cluster-level 리소스이다. 반면 PVC 는 namespace 를 갖는다. path: /tmp/mongodb
```
### 6.5.3 PersistentVolumeClaim 을 생성하여 PersistentVolume 연결하기
크기와, 접근 방식, 그리고 reclaim 될 때의 동작, 그리고 실제 물리적인 저장소 정보 (위 예시에서는 `hostPath`) 를 명시해줘야 한다.
Pod 에서 직접 접속해서는 안되고, (혹시 가능할까 ㅋㅋ) claim 을 먼저 해야한다. 그런데 claim 은 pod 생성과 독립적인 과정이다. Pod 에 종속적인 작업이 된다면 pod 이 re-스케쥴링 되거나 재시작될 때 또 claim 을 해야한다. 애초에 목적이 *persistent* volume 을 만드는 것이므로 pod 생성과는 독립적인 것이 자연스럽다. 재시작 되어도 저장소는 그대로 있어야지!
참고로 PV 는 namespace 를 갖지 않는다. Cluster-level 리소스이다. 반면 PVC 는 namespace 를 갖는다.
```yaml
apiVersion: v1 ### 6.5.3 PersistentVolumeClaim 을 생성하여 PersistentVolume 연결하기
kind: PersistentVolumeClaim
metadata: Pod 에서 직접 접속해서는 안되고, (혹시 가능할까 ㅋㅋ) claim 을 먼저 해야한다. 그런데 claim 은 pod 생성과 독립적인 과정이다. Pod 에 종속적인 작업이 된다면 pod 이 re-스케쥴링 되거나 재시작될 때 또 claim 을 해야한다. 애초에 목적이 *persistent* volume 을 만드는 것이므로 pod 생성과는 독립적인 것이 자연스럽다. 재시작 되어도 저장소는 그대로 있어야지!
name: mongodb-pvc
spec: ```yaml
resources: apiVersion: v1
requests: kind: PersistentVolumeClaim
storage: 1Gi # 1GB 를 요청한다 metadata:
accessModes: # 연결할 PV 는 아래 옵션으로 접근이 가능해야 한다 name: mongodb-pvc
- ReadWriteOnce spec:
storageClassName: "" # 추후 dynamic provisioning 에서 설명 resources:
``` requests:
storage: 1Gi # 1GB 를 요청한다
PVC 를 생성하면 적합한 PV 를 찾아 bind 해준다. PVC 에서 요청한 공간보다 PV 의 용량이 커야 하고, PVC 에서 요구한 `accessModes` 를 PV 또한 지원해야 한다. accessModes: # 연결할 PV 는 아래 옵션으로 접근이 가능해야 한다
- ReadWriteOnce
#### Access Modes storageClassName: "" # 추후 dynamic provisioning 에서 설명
```
- `RWO`: `ReadWriteOnce`
- `ROX`: `ReadOnlyMany` PVC 를 생성하면 적합한 PV 를 찾아 bind 해준다. PVC 에서 요청한 공간보다 PV 의 용량이 커야 하고, PVC 에서 요구한 `accessModes` 를 PV 또한 지원해야 한다.
- `RWX`: `ReadWriteMany`
#### Access Modes
추가로, `Once` 와 `Many` 를 구분하는 기준은 *노드* 이다. Pod 의 개수가 아니다.
- `RWO`: `ReadWriteOnce`
### 6.5.4 Pod 안에서 PersistentVolumeClaim 사용하기 - `ROX`: `ReadOnlyMany`
- `RWX`: `ReadWriteMany`
PV 를 직접 연결하지 말고, PVC 를 reference 하도록 한다.
추가로, `Once``Many` 를 구분하는 기준은 *노드* 이다. Pod 의 개수가 아니다.
```yaml
apiVersion: v1 ### 6.5.4 Pod 안에서 PersistentVolumeClaim 사용하기
kind: Pod
metadata: PV 를 직접 연결하지 말고, PVC 를 reference 하도록 한다.
name: mongodb
spec: ```yaml
containers: apiVersion: v1
- image: mongo kind: Pod
name: mongodb metadata:
volumeMounts: name: mongodb
- name: mongodb-data spec:
mountPath: /data/db containers:
ports: - image: mongo
- containerPort: 27017 name: mongodb
protocol: TCP volumeMounts:
volumes: - name: mongodb-data
- name: mongodb-data mountPath: /data/db
persistentVolumeClaim: ports:
claimName: mongodb-pvc # PVC 의 이름 - containerPort: 27017
``` protocol: TCP
volumes:
### 6.5.5 PersistentVolume, PersistentVolumeClaim 의 장점 - name: mongodb-data
persistentVolumeClaim:
일단 개발자 입장에서는 개발자가 클러스터 환경에 대해 이해하고 있지 않아도 돼서 간편하다. 또한 PVC 를 만들기 위해 사용한 YAML 파일은 클러스터 환경과 관계없이 동작하게 되었다. 단지 PV 가 어떤 요구사항을 충족해야 하는지만 설명하면 된다. claimName: mongodb-pvc # PVC 의 이름
```
(PV 는 여전히 클러스터 환경에 영향을 받는 것 같은데 이는 어쩔 수 없는 것인가...)
### 6.5.5 PersistentVolume, PersistentVolumeClaim 의 장점
### 6.5.6 PersistentVolume 정리하고 재사용하기
일단 개발자 입장에서는 개발자가 클러스터 환경에 대해 이해하고 있지 않아도 돼서 간편하다. 또한 PVC 를 만들기 위해 사용한 YAML 파일은 클러스터 환경과 관계없이 동작하게 되었다. 단지 PV 가 어떤 요구사항을 충족해야 하는지만 설명하면 된다.
Pod 과 PVC 를 지우고 `kubectl get pv` 를 해보면 `Released` 상태로 바뀐 것을 확인할 수 있다. 다시 PVC 를 생성하고 `kubectl get pv` 를 해보면 상태가 변하지 않았다. `kubectl get pvc` 를 해보면 `Pending` 상태인 것도 확인할 수 있다.
(PV 는 여전히 클러스터 환경에 영향을 받는 것 같은데 이는 어쩔 수 없는 것인가...)
이는 PV 를 한 번 사용한 뒤 클러스터 관리자가 해당 PV 를 정리할 기회를 주기 위해서이다. PV 는 namespace 에 종속되지 않기 때문에 다른 namespace 의 PVC 에서도 bind 요청을 보낼 수 있다. 하지만 PV 를 정리하지 않고 그냥 연결해주게 되면 전에 연결되었던 다른 namespace 의 pod 에서 작업했던 내용이 그대로 남아있을 것이다. (다른 namespace 면 분리하고 싶은 것이니 작업 내용이 보이지 않아야 한다)
### 6.5.6 PersistentVolume 정리하고 재사용하기
#### 수동으로 reclaim 하기
Pod 과 PVC 를 지우고 `kubectl get pv` 를 해보면 `Released` 상태로 바뀐 것을 확인할 수 있다. 다시 PVC 를 생성하고 `kubectl get pv` 를 해보면 상태가 변하지 않았다. `kubectl get pvc` 를 해보면 `Pending` 상태인 것도 확인할 수 있다.
위와 같은 현상은 `persistentVolumeReclaimPolicy: Retain` 으로 설정했기 때문에 일어났다. 수동으로 reclaim 하려면 PV 를 지우고 다시 생성하는 방법밖에 없다고 한다. PV 안에 있던 파일은 꺼내올 수 있다.
이는 PV 를 한 번 사용한 뒤 클러스터 관리자가 해당 PV 를 정리할 기회를 주기 위해서이다. PV 는 namespace 에 종속되지 않기 때문에 다른 namespace 의 PVC 에서도 bind 요청을 보낼 수 있다. 하지만 PV 를 정리하지 않고 그냥 연결해주게 되면 전에 연결되었던 다른 namespace 의 pod 에서 작업했던 내용이 그대로 남아있을 것이다. (다른 namespace 면 분리하고 싶은 것이니 작업 내용이 보이지 않아야 한다)
#### 자동으로 reclaim 하기
#### 수동으로 reclaim 하기
다른 reclaim policy 로는 `Recycle`, `Delete` 가 있다. `Recycle` 은 지우고 새로 만들어서 다른 PVC 가 사용할 수 있도록 한다. `Delete` 는 그냥 지워버린다.
위와 같은 현상은 `persistentVolumeReclaimPolicy: Retain` 으로 설정했기 때문에 일어났다. 수동으로 reclaim 하려면 PV 를 지우고 다시 생성하는 방법밖에 없다고 한다. PV 안에 있던 파일은 꺼내올 수 있다.
## 6.6 Dynamic provisioning of PersistentVolumes
#### 자동으로 reclaim 하기
위에서 살펴본 방식대로 PVC 를 사용하는 것도 편하지만, 여전히 클러스터 관리자가 PV 를 생성해야한다는 점에서 불편하다. 쿠버네티스는 PV 의 dynamic provisioning 을 제공하여 스토리지를 자동으로 만들어준다.
다른 reclaim policy 로는 `Recycle`, `Delete` 가 있다. `Recycle` 은 지우고 새로 만들어서 다른 PVC 가 사용할 수 있도록 한다. `Delete` 는 그냥 지워버린다.
클러스터 관리자는 PV 를 만들지 않고 PV provisioner 를 만들어 두고, StorageClass object 를 여러 개 정의해둘 수 있다. 그러면 사용자는 어떤 타입의 PV 를 원하는지 설정할 수 있다.
## 6.6 Dynamic provisioning of PersistentVolumes
> StorageClass objects aren't namespaced! ---
그러므로 관리자가 PV 를 여러 개 만들어 두지 않아도 되고, 한 두개 정도 StorageClass 를 정의해 두면 PVC통해 요청이 들어올 때 쿠버네티스 시스템이 알아서 생성해준다. 이렇게 하면 PV 가 부족할 일은 없을 것이다. (하지만 저장 공간은 가득 찰 수도 있다) 위에서 살펴본 방식대로 PVC사용하는 것도 편하지만, 여전히 클러스터 관리자가 PV 를 생성해야한다는 점에서 불편하다. 쿠버네티스는 PV 의 dynamic provisioning 을 제공하여 스토리지를 자동으로 만들어준다.
### 6.6.1 StorageClass 리소스로 이용 가능한 스토리지 종류 정의하기 클러스터 관리자는 PV 를 만들지 않고 PV provisioner 를 만들어 두고, StorageClass object 를 여러 개 정의해둘 수 있다. 그러면 사용자는 어떤 타입의 PV 를 원하는지 설정할 수 있다.
```yaml > StorageClass objects aren't namespaced!
apiVersion: storage.k8s.io/v1
kind: StorageClass 그러므로 관리자가 PV 를 여러 개 만들어 두지 않아도 되고, 한 두개 정도 StorageClass 를 정의해 두면 PVC 를 통해 요청이 들어올 때 쿠버네티스 시스템이 알아서 생성해준다. 이렇게 하면 PV 가 부족할 일은 없을 것이다. (하지만 저장 공간은 가득 찰 수도 있다)
metadata:
name: fast ### 6.6.1 StorageClass 리소스로 이용 가능한 스토리지 종류 정의하기
provisioner: k8s.io/minikube-hostpath # volume plugin
parameters: ```yaml
type: pd-ssd # provisioner 로 전달되는 parameter apiVersion: storage.k8s.io/v1
``` kind: StorageClass
metadata:
PVC 가 StorageClass 에 요청을 할 때 어떤 provisioner 를 사용할지 설정할 수 있다. Provisioner plugin 마다 parameter 가 조금씩 다를 수 있다. name: fast
provisioner: k8s.io/minikube-hostpath # volume plugin
### 6.6.2 PersistentVolumeClaim 에서 StorageClass 요청하기 parameters:
type: pd-ssd # provisioner 로 전달되는 parameter
```yaml ```
...
spec: PVC 가 StorageClass 에 요청을 할 때 어떤 provisioner 를 사용할지 설정할 수 있다. Provisioner plugin 마다 parameter 가 조금씩 다를 수 있다.
storageClassName: fast # StorageClass 이름
resources: ### 6.6.2 PersistentVolumeClaim 에서 StorageClass 요청하기
requests:
storage: 100Mi ```yaml
accessModes: ...
- ReadWriteOnce spec:
``` storageClassName: fast # StorageClass 이름
resources:
StorageClass 를 명시할 수 있다. PVC 를 생성하면 StorageClass 가 PV 를 만들어 준다. 만약 존재하지 않는 StorageClass 를 입력하면 `ProvisioningFailed` 에러가 발생한다. requests:
storage: 100Mi
#### StorageClass 사용법 이해하기 accessModes:
- ReadWriteOnce
클러스터 관리자가 다양한 StorageClass 를 만들어둘 수 있다. 개발자는 그 중 적절한 StorageClass 를 골라서 사용하면 된다. ```
PVC 를 설정할 때 StorageClass 의 이름으로 하기 때문에 클라우드 환경과 무관하게 해당 StorageClass 이름이 있다면 동작하게 된다. (portable!) StorageClass 를 명시할 수 있다. PVC 를 생성하면 StorageClass 가 PV 를 만들어 준다. 만약 존재하지 않는 StorageClass 를 입력하면 `ProvisioningFailed` 에러가 발생한다.
(아무튼 StorageClass 는 수동으로 만들어야 하는 것인가) #### StorageClass 사용법 이해하기
### 6.6.3 Dynamic provisioning without specifying a storage class 클러스터 관리자가 다양한 StorageClass 를 만들어둘 수 있다. 개발자는 그 중 적절한 StorageClass 를 골라서 사용하면 된다.
`kubectl get sc` 를 해보면 `standard` 라는 이름을 가진 StorageClass 가 있는 것을 확인할 수 있다. PVC 를 생성할 때 `storageClassName` 값을 주지 않고 생성하면 기본값인 `standard` 를 이용하게 된다. PVC 를 설정할 때 StorageClass 의 이름으로 하기 때문에 클라우드 환경과 무관하게 해당 StorageClass 이름이 있다면 동작하게 된다. (portable!)
그래서 위에서 `storageClassName: ""` 로 설정한 이유는 이렇게 empty string 을 넣어주지 않으면 `standard` StorageClass 를 이용하여 새로운 PV 를 만들기 때문이다. Empty string 을 넣어주면 bind 할 PV 를 이미 존재하는 PV 중에서 찾는다. (아무튼 StorageClass 는 수동으로 만들어야 하는 것인가)
#### Complete picture of dynamic provisioning ### 6.6.3 Dynamic provisioning without specifying a storage class
정리하면, pod 에 persistent storage 를 붙이는 최고의 방법은 PVC (with or without StorageClass) 만 생성하는 것이다. 나머지는 dynamic provisioner 가 해결해준다. `kubectl get sc` 를 해보면 `standard` 라는 이름을 가진 StorageClass 가 있는 것을 확인할 수 있다. PVC 를 생성할 때 `storageClassName` 값을 주지 않고 생성하면 기본값인 `standard` 를 이용하게 된다.
1. 클러스터 관리자가 PV provisioner 를 설정한다. 그래서 위에서 `storageClassName: ""` 로 설정한 이유는 이렇게 empty string 을 넣어주지 않으면 `standard` StorageClass 를 이용하여 새로운 PV 를 만들기 때문이다. Empty string 을 넣어주면 bind 할 PV 를 이미 존재하는 PV 중에서 찾는다.
2. 관리자가 StorageClass 를 만들어 둔다. 그리고 필요하다면 하나를 기본값으로 설정한다.
3. 사용자는 (StorageClass 를 이용하여) PVC 을 생성한다. #### Complete picture of dynamic provisioning
4. K8s 가 해당 StorageClass 의 provisioner 에게 새로운 PV 를 만들 것을 요청한다. 이 때 PVC 의 요구 사항 (용량, 접근 모드) 과 StorageClass 에 적은 parameter 가 함께 전달된다.
5. Provisioner 가 실제 저장소를 만들고 PV 를 만들어 PVC 에 bind 한다. 정리하면, pod 에 persistent storage 를 붙이는 최고의 방법은 PVC (with or without StorageClass) 만 생성하는 것이다. 나머지는 dynamic provisioner 가 해결해준다.
6. 사용자가 해당 PVC 를 이름으로 reference 하는 pod 를 생성한다.
1. 클러스터 관리자가 PV provisioner 를 설정한다.
--- 2. 관리자가 StorageClass 를 만들어 둔다. 그리고 필요하다면 하나를 기본값으로 설정한다.
3. 사용자는 (StorageClass 를 이용하여) PVC 을 생성한다.
## Discussion & Additional Topics 4. K8s 가 해당 StorageClass 의 provisioner 에게 새로운 PV 를 만들 것을 요청한다. 이 때 PVC 의 요구 사항 (용량, 접근 모드) 과 StorageClass 에 적은 parameter 가 함께 전달된다.
5. Provisioner 가 실제 저장소를 만들고 PV 를 만들어 PVC 에 bind 한다.
### What is NFS? 6. 사용자가 해당 PVC 를 이름으로 reference 하는 pod 를 생성한다.
- Network File System ---
- https://en.wikipedia.org/wiki/Network_File_System
## Discussion & Additional Topics
### Why do PersistentVolum(Claim) access modes pertain to the number of worker nodes, not to the number of pods?
### What is NFS?
- 왜 이렇게 했지?
- (추정) 클라우드에서 volume 이라는 시스템 자체가 노드 단위로 컨트롤 되기 때문에? - Network File System
- https://en.wikipedia.org/wiki/Network_File_System
### 올바른 PersistentVolume 의 사용법?
### Why do PersistentVolum(Claim) access modes pertain to the number of worker nodes, not to the number of pods?
- Reclaim policy 가 `Retain`, `Recycle`, `Delete` 뿐이면 재사용이 안된다는 것인데, 그렇다면 PVC 를 생성해서 작업을 하는 동안 계속 연결해야 하고 pod 들의 작업이 끝나야지만 지우라는 건가?
- PVC 를 지워서 연결은 끊었지만 나중에 다시 필요하다면...? 애초에 그런 상황을 만들지 말고 PVC 를 계속 유지? - 왜 이렇게 했지?
- 그런 듯 하다. - (추정) 클라우드에서 volume 이라는 시스템 자체가 노드 단위로 컨트롤 되기 때문에?
### `hostPath` use case ### 올바른 PersistentVolume 의 사용법?
- GPU 가 필요한 노드에서 DaemonSet 으로 노드에 Nvidia driver 를 설치하고 driver path 를 `hostPath` 로 잡는 경우가 있다. - Reclaim policy 가 `Retain`, `Recycle`, `Delete` 뿐이면 재사용이 안된다는 것인데, 그렇다면 PVC 를 생성해서 작업을 하는 동안 계속 연결해야 하고 pod 들의 작업이 끝나야지만 지우라는 건가?
- PVC 를 지워서 연결은 끊었지만 나중에 다시 필요하다면...? 애초에 그런 상황을 만들지 말고 PVC 를 계속 유지?
### Storage vs Disk vs Volume vs Filesystem - 그런 듯 하다.
- 스토리지: 추상화된 저장소 ### `hostPath` use case
- 디스크: 저장소의 물리적 구현체 (HDD, SSD)
- 볼륨: 디스크의 한 구획 (파티션, C:, D;) - GPU 가 필요한 노드에서 DaemonSet 으로 노드에 Nvidia driver 를 설치하고 driver path 를 `hostPath` 로 잡는 경우가 있다.
- 파일시스템: 디스크에 파일을 저장하는 소프트웨어 (ext, NTFS etc.)
### Storage vs Disk vs Volume vs Filesystem
- 스토리지: 추상화된 저장소
- 디스크: 저장소의 물리적 구현체 (HDD, SSD)
- 볼륨: 디스크의 한 구획 (파티션, C:, D;)
- 파일시스템: 디스크에 파일을 저장하는 소프트웨어 (ext, NTFS etc.)

View File

@@ -1,474 +1,474 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops] tags: [kubernetes, sre, devops]
title: "07. ConfigMaps and Secrets: Configuring Applications" title: "07. ConfigMaps and Secrets: Configuring Applications"
date: "2021-04-18" date: "2021-04-18"
github_title: "2021-04-18-07-configmaps-and-secrets" github_title: "2021-04-18-07-configmaps-and-secrets"
image: image:
path: /assets/img/posts/k8s-07.jpeg path: /assets/img/posts/k8s-07.jpeg
--- ---
![k8s-07.jpeg](../../../assets/img/posts/k8s-07.jpeg) _Combining a ConfigMap and a Secret to run your fortune-https pod (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-7)_ ![k8s-07.jpeg](../../../assets/img/posts/k8s-07.jpeg) _Combining a ConfigMap and a Secret to run your fortune-https pod (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-7)_
거의 대부분의 앱은 설정(configuration)이 필요하다. 개발 서버, 배포 서버의 설정 사항 (접속하려는 DB 서버 주소 등)이 다를 수도 있고, 클라우드 등에 접속하기 위한 access key 가 필요하거나, 데이터를 암호화하는 encryption key 도 설정해야하는 경우가 있다. 이러한 경우에 해당 값들을 도커 이미지 자체에 넣어버리면 보안 상 취약하고, 또 설정 사항을 변경하는 경우 이미지를 다시 빌드해야하는 등 불편함이 따른다. 거의 대부분의 앱은 설정(configuration)이 필요하다. 개발 서버, 배포 서버의 설정 사항 (접속하려는 DB 서버 주소 등)이 다를 수도 있고, 클라우드 등에 접속하기 위한 access key 가 필요하거나, 데이터를 암호화하는 encryption key 도 설정해야하는 경우가 있다. 이러한 경우에 해당 값들을 도커 이미지 자체에 넣어버리면 보안 상 취약하고, 또 설정 사항을 변경하는 경우 이미지를 다시 빌드해야하는 등 불편함이 따른다.
이번 장에서는 Kubernetes 에서 돌아가는 애플리케이션에 설정 사항을 넘겨주는 방법을 알아본다. 이번 장에서는 Kubernetes 에서 돌아가는 애플리케이션에 설정 사항을 넘겨주는 방법을 알아본다.
## 7.1 컨테이너화 된 애플리케이션 설정하기 ## 7.1 컨테이너화 된 애플리케이션 설정하기
--- ---
보통 애플리케이션의 설정 사항을 관리할 때에는 configuration file 이 존재하게 된다. (`.properties`, `.env` 등) 보통 애플리케이션의 설정 사항을 관리할 때에는 configuration file 이 존재하게 된다. (`.properties`, `.env` 등)
그런데 Docker 를 사용하면, config file 을 컨테이너로 옮기는 명령이 Dockerfile 에 필요하게 되고, config file 을 수정하면 이미지를 다시 빌드해야하기 때문에 보통은 컨테이너에 환경 변수(environment variables)를 전달하는 방식으로 사용한다. 그리고 애플리케이션은 환경 변수를 조회하여 사용할 수 있다. 그런데 Docker 를 사용하면, config file 을 컨테이너로 옮기는 명령이 Dockerfile 에 필요하게 되고, config file 을 수정하면 이미지를 다시 빌드해야하기 때문에 보통은 컨테이너에 환경 변수(environment variables)를 전달하는 방식으로 사용한다. 그리고 애플리케이션은 환경 변수를 조회하여 사용할 수 있다.
또는 6장에서 배운 volume 을 사용할 수도 있을 것이다. 또는 6장에서 배운 volume 을 사용할 수도 있을 것이다.
애플리케이션 설정 사항을 전달하는 방법에는 크게 3가지가 있다. 애플리케이션 설정 사항을 전달하는 방법에는 크게 3가지가 있다.
- 컨테이너에 command line argument 전달하기 - 컨테이너에 command line argument 전달하기
- 컨테이너마다 환경 변수 설정하기 - 컨테이너마다 환경 변수 설정하기
- Volume 을 이용해서 config file mount 하기 - Volume 을 이용해서 config file mount 하기
## 7.2 컨테이너에 command line argument 전달하기 ## 7.2 컨테이너에 command line argument 전달하기
--- ---
보통은 컨테이너 이미지에 정의된 기본 명령으로 이미지를 실행하지만, Kubernetes 에서는 해당 명령을 override 하여 다른 명령을 실행하도록 할 수 있다. 그래서 실행할 때 추가로 argument 를 전달할 수 있게 된다. 보통은 컨테이너 이미지에 정의된 기본 명령으로 이미지를 실행하지만, Kubernetes 에서는 해당 명령을 override 하여 다른 명령을 실행하도록 할 수 있다. 그래서 실행할 때 추가로 argument 를 전달할 수 있게 된다.
### 7.2.1 Defining the command and arguments in Docker ### 7.2.1 Defining the command and arguments in Docker
우선, 컨테이너에서 실행되는 명령은 두 부분: **command**, **arguments** 으로 나뉜다. 우선, 컨테이너에서 실행되는 명령은 두 부분: **command**, **arguments** 으로 나뉜다.
#### `ENTRYPOINT`, `CMD` 이해하기 #### `ENTRYPOINT`, `CMD` 이해하기
Dockerfile 에서 다음 명령을 사용할 수 있다. Dockerfile 에서 다음 명령을 사용할 수 있다.
- `ENTRYPOINT`: 컨테이너가 시작할 때 실행할 파일 - `ENTRYPOINT`: 컨테이너가 시작할 때 실행할 파일
- `CMD`: `ENTRYPOINT` 를 실행할 때 전달할 argument - `CMD`: `ENTRYPOINT` 를 실행할 때 전달할 argument
> **Note.** Dockerfile reference 를 보면 `CMD` 의 용법은 3가지가 있다고 한다. > **Note.** Dockerfile reference 를 보면 `CMD` 의 용법은 3가지가 있다고 한다.
> - `CMD ["executable","param1","param2"]` (*exec* form, this is the preferred form) > - `CMD ["executable","param1","param2"]` (*exec* form, this is the preferred form)
> - `CMD ["param1","param2"]` (as *default parameters to ENTRYPOINT*) > - `CMD ["param1","param2"]` (as *default parameters to ENTRYPOINT*)
> - `CMD command param1 param2` (*shell* form) > - `CMD command param1 param2` (*shell* form)
> >
> Dockerfile 에는 하나의 `CMD` 만 존재할 수 있으며, **The main purpose of a CMD is to provide defaults for an executing container.** 라고 한다. > Dockerfile 에는 하나의 `CMD` 만 존재할 수 있으며, **The main purpose of a CMD is to provide defaults for an executing container.** 라고 한다.
> *provide default* 라고 했기 때문에 이는 overriding 이 가능하다는 것이다. > *provide default* 라고 했기 때문에 이는 overriding 이 가능하다는 것이다.
> >
> `ENTRYPOINT` 를 사용하면 컨테이너가 실행될 때 `ENTRYPOINT` 에서 지정한 명령을 수행하고, `CMD` 도 마찬가지지만 `CMD` 의 경우 컨테이너 실행시 인자값을 주면 Dockerfile 의 `CMD` 를 override 하여 실행한다. > `ENTRYPOINT` 를 사용하면 컨테이너가 실행될 때 `ENTRYPOINT` 에서 지정한 명령을 수행하고, `CMD` 도 마찬가지지만 `CMD` 의 경우 컨테이너 실행시 인자값을 주면 Dockerfile 의 `CMD` 를 override 하여 실행한다.
> >
> Reference 에서도 이 둘의 사용법을 설명해줬다. > Reference 에서도 이 둘의 사용법을 설명해줬다.
> >
> Both `CMD` and `ENTRYPOINT` instructions define what command gets executed when running a container. There are few rules that describe their co-operation. > Both `CMD` and `ENTRYPOINT` instructions define what command gets executed when running a container. There are few rules that describe their co-operation.
> - Dockerfile should specify at least one of `CMD` or `ENTRYPOINT` commands. > - Dockerfile should specify at least one of `CMD` or `ENTRYPOINT` commands.
> - `ENTRYPOINT` should be defined when using the container as an executable. > - `ENTRYPOINT` should be defined when using the container as an executable.
> - `CMD` should be used as a way of defining default arguments for an `ENTRYPOINT` command or for executing an ad-hoc command in a container. > - `CMD` should be used as a way of defining default arguments for an `ENTRYPOINT` command or for executing an ad-hoc command in a container.
> - `CMD` will be overridden when running the container with alternative arguments. > - `CMD` will be overridden when running the container with alternative arguments.
#### shell form & exec form #### shell form & exec form
`ENTRYPOINT`, `CMD` 를 사용할 때는 2가지 형태가 있다. `ENTRYPOINT`, `CMD` 를 사용할 때는 2가지 형태가 있다.
- `shell` form: `ENTRYPOINT node app.js` - `shell` form: `ENTRYPOINT node app.js`
- `exec` form: `ENTRYPOINT ["node", "app.js"]` - `exec` form: `ENTRYPOINT ["node", "app.js"]`
`shell` form 의 경우에는 컨테이너가 시작하면 shell 을 띄워서 안에서 명령을 실행한다. 반면 `exec` form 은 해당 명령이 바로 컨테이너의 프로세스가 된다. `shell` form 의 경우에는 컨테이너가 시작하면 shell 을 띄워서 안에서 명령을 실행한다. 반면 `exec` form 은 해당 명령이 바로 컨테이너의 프로세스가 된다.
그러므로 전자는 `shell` 프로세스를 별도로 실행하기 때문에, `exec` form 을 사용하는 것이 좋다. 그러므로 전자는 `shell` 프로세스를 별도로 실행하기 때문에, `exec` form 을 사용하는 것이 좋다.
#### 이미지에서 argument 설정하기 #### 이미지에서 argument 설정하기
```dockerfile ```dockerfile
ENTRYPOINT ["/bin/fortuneloop.sh"] ENTRYPOINT ["/bin/fortuneloop.sh"]
CMD ["10"] CMD ["10"]
``` ```
위와 같이 설정하면 쉘 스크립트의 argument 로 `10` 을 전달할 수 있다. 위와 같이 설정하면 쉘 스크립트의 argument 로 `10` 을 전달할 수 있다.
또 `docker run` 할 때 override 할 수 있다. `docker run` 할 때 override 할 수 있다.
```bash ```bash
$ docker run -it <IMAGE_NAME> 15 $ docker run -it <IMAGE_NAME> 15
``` ```
위와 같이 넘기면 `15` 를 argument 로 전달할 수 있다. 위와 같이 넘기면 `15` 를 argument 로 전달할 수 있다.
### 7.2.2 Kubernetes 에서 command, argument overriding ### 7.2.2 Kubernetes 에서 command, argument overriding
YAML 파일에서 override 할 수 있다. YAML 파일에서 override 할 수 있다.
```yaml ```yaml
spec: spec:
containers: containers:
- image: some/image - image: some/image
command: ["/bin/command"] command: ["/bin/command"]
args: ["arg1", "arg2", "arg3"] args: ["arg1", "arg2", "arg3"]
``` ```
`command`, `args` 필드는 컨테이너가 시작되면 수정할 수 없다. `command`, `args` 필드는 컨테이너가 시작되면 수정할 수 없다.
## 7.3 컨테이너 환경 변수 설정하기 ## 7.3 컨테이너 환경 변수 설정하기
--- ---
Pod 레벨에서 환경 변수를 설정하고 컨테이너가 이를 상속하게 하는 옵션은 존재하지 않는다. Pod 레벨에서 환경 변수를 설정하고 컨테이너가 이를 상속하게 하는 옵션은 존재하지 않는다.
### 7.3.1 컨데이너 정의에 환경 변수 설정하기 ### 7.3.1 컨데이너 정의에 환경 변수 설정하기
```yaml ```yaml
spec: spec:
containers: containers:
- image: luksa/fortune:env - image: luksa/fortune:env
env: env:
- name: INTERVAL - name: INTERVAL
value: "30" value: "30"
name: html-generator name: html-generator
``` ```
위와 같이 `env` 필드 안에 환경 변수의 이름과 값을 설정할 수 있다. 확인해 보면 pod 레벨에서 환경 변수를 설정하는 것이 아니라 컨테이너 별로 환경 변수를 설정해야 한다. 위와 같이 `env` 필드 안에 환경 변수의 이름과 값을 설정할 수 있다. 확인해 보면 pod 레벨에서 환경 변수를 설정하는 것이 아니라 컨테이너 별로 환경 변수를 설정해야 한다.
### 7.3.2 환경 변수 참조하기 ### 7.3.2 환경 변수 참조하기
이전에 정의된 환경 변수를 재사용할 수도 있다. 이전에 정의된 환경 변수를 재사용할 수도 있다.
```yaml ```yaml
env: env:
- name: FIRST_VAR - name: FIRST_VAR
value: "foo" value: "foo"
- name: SECOND_VAR - name: SECOND_VAR
value: "$(FIRST_VAR)bar" value: "$(FIRST_VAR)bar"
``` ```
더불어 `command`, `args` 필드의 값에서도 환경 변수를 참조하여 사용할 수 있다. 더불어 `command`, `args` 필드의 값에서도 환경 변수를 참조하여 사용할 수 있다.
### 7.3.3 환경 변수 하드코딩의 단점 ### 7.3.3 환경 변수 하드코딩의 단점
위와 같은 방법으로 하면 환경 변수를 하나씩 하드코딩하는 것인데, 그렇다면 개발용/배포용 pod 설정을 따로 해줘야 하는 불편함이 있다. Pod 설정을 재사용하지 못하는 것이다. 위와 같은 방법으로 하면 환경 변수를 하나씩 하드코딩하는 것인데, 그렇다면 개발용/배포용 pod 설정을 따로 해줘야 하는 불편함이 있다. Pod 설정을 재사용하지 못하는 것이다.
만약 pod 설정을 재사용하고 싶다면 config 와 pod 설정을 분리해야 하므로, 쿠버네티스에서는 ConfigMap 리소스를 제공한다. 만약 pod 설정을 재사용하고 싶다면 config 와 pod 설정을 분리해야 하므로, 쿠버네티스에서는 ConfigMap 리소스를 제공한다.
## 7.4 ConfigMap 으로 설정 분리하기 ## 7.4 ConfigMap 으로 설정 분리하기
--- ---
앱 설정 사항을 만들 때 가장 많이 고려하는 부분은 자주 바뀌는 설정을 코드와 분리하는 것이다. (그래서 config file 도 만들고, 환경 변수도 쓰고...) 앱 설정 사항을 만들 때 가장 많이 고려하는 부분은 자주 바뀌는 설정을 코드와 분리하는 것이다. (그래서 config file 도 만들고, 환경 변수도 쓰고...)
### 7.4.1 ConfigMap ### 7.4.1 ConfigMap
ConfigMap 는 환경 변수를 key/value pair 로 저장하는 리소스이다. Value 에는 짧은 string 뿐만 아니라 config file 자체가 들어갈 수도 있다. ConfigMap 는 환경 변수를 key/value pair 로 저장하는 리소스이다. Value 에는 짧은 string 뿐만 아니라 config file 자체가 들어갈 수도 있다.
애플리케이션 입장에서는 ConfigMap 을 읽어올 필요도 없고 존재하는지 알 필요도 없다. 대신 ConfigMap 리소스의 정보는 volume 이 되어서 환경 변수나 파일로 컨테이너에 전달된다. 애플리케이션 입장에서는 ConfigMap 을 읽어올 필요도 없고 존재하는지 알 필요도 없다. 대신 ConfigMap 리소스의 정보는 volume 이 되어서 환경 변수나 파일로 컨테이너에 전달된다.
또한 pod 들이 ConfigMap 을 레퍼런스 할 때 이름으로 레퍼런스 하기 때문에, ConfigMap 을 같은 이름으로 여러 개 만들고 각각 다른 namespace 에 만들게 되면 pod YAML 파일을 하나만 만들어서 여러 namespace 에 재사용할 수 있게 된다. 또한 pod 들이 ConfigMap 을 레퍼런스 할 때 이름으로 레퍼런스 하기 때문에, ConfigMap 을 같은 이름으로 여러 개 만들고 각각 다른 namespace 에 만들게 되면 pod YAML 파일을 하나만 만들어서 여러 namespace 에 재사용할 수 있게 된다.
(namespace 별로 이름은 같지만 다른 설정을 할 수 있다는 의미) (namespace 별로 이름은 같지만 다른 설정을 할 수 있다는 의미)
### 7.4.2 ConfigMap 만들기 ### 7.4.2 ConfigMap 만들기
`kubectl create configmap` 으로 만들면 된다. `kubectl create configmap` 으로 만들면 된다.
#### Literal 으로부터 만들기 #### Literal 으로부터 만들기
```bash ```bash
$ kubectl create configmap <NAME> --from-literal=<KEY>=<VALUE> $ kubectl create configmap <NAME> --from-literal=<KEY>=<VALUE>
``` ```
위 명령을 실행하면 ConfigMap 을 만들고 `<KEY>=<VALUE>` 로 설정이 생긴다. 여러 개를 만들고 싶다면 `--from-literal` 을 여러 개 입력해야한다. (불편) 위 명령을 실행하면 ConfigMap 을 만들고 `<KEY>=<VALUE>` 로 설정이 생긴다. 여러 개를 만들고 싶다면 `--from-literal` 을 여러 개 입력해야한다. (불편)
만약 YAML 로 만들게 된다면 `.metadata.name` 에 ConfigMap 이름을 설정해 주고, 나머지는 `data` 에 key-value pair 로 넣어주면 된다. 만약 YAML 로 만들게 된다면 `.metadata.name` 에 ConfigMap 이름을 설정해 주고, 나머지는 `data` 에 key-value pair 로 넣어주면 된다.
#### 파일로부터 만들기 #### 파일로부터 만들기
```bash ```bash
$ kubectl create configmap <NAME> --from-file=<FILENAME> $ kubectl create configmap <NAME> --from-file=<FILENAME>
``` ```
주의할 점은 `kubectl` 을 실행하는 경로 내에서 `<FILENAME>` (파일)을 찾는다는 점이다. 실행하게 되면 파일 이름을 key 로 해서 파일의 내용을 저장하게 된다. (key 도 임의로 설정할 수 있다) 주의할 점은 `kubectl` 을 실행하는 경로 내에서 `<FILENAME>` (파일)을 찾는다는 점이다. 실행하게 되면 파일 이름을 key 로 해서 파일의 내용을 저장하게 된다. (key 도 임의로 설정할 수 있다)
여러 파일을 추가하는 경우에도 `--from-file` 을 여러 번 적어야 한다. 여러 파일을 추가하는 경우에도 `--from-file` 을 여러 번 적어야 한다.
#### 디렉토리 내의 모든 파일 추가 #### 디렉토리 내의 모든 파일 추가
`--from-file` 옵션을 이용할 때 디렉토리 경로를 넣어주면 된다. 대신 파일 이름이 유효한 key 값이어야 한다. (valid DNS subdomain) `--from-file` 옵션을 이용할 때 디렉토리 경로를 넣어주면 된다. 대신 파일 이름이 유효한 key 값이어야 한다. (valid DNS subdomain)
#### 옵션 조합하기 #### 옵션 조합하기
ConfigMap 을 만들 때 위 옵션들을 조합해서 전부 사용할 수 있다. ConfigMap 을 만들 때 위 옵션들을 조합해서 전부 사용할 수 있다.
### 7.4.3 ConfigMap entry 를 컨테이너의 환경 변수로 사용하기 ### 7.4.3 ConfigMap entry 를 컨테이너의 환경 변수로 사용하기
`valueFrom` 을 사용한다. `valueFrom` 을 사용한다.
```yaml ```yaml
spec: spec:
containers: containers:
- image: luksa/fortune:env - image: luksa/fortune:env
env: env:
- name: INTERVAL # 환경 변수를 만든다 - name: INTERVAL # 환경 변수를 만든다
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: fortune-config # fortune-config 라는 ConfigMap 에서 name: fortune-config # fortune-config 라는 ConfigMap 에서
key: sleep-interval # sleep-interval 의 값을 가져온다 key: sleep-interval # sleep-interval 의 값을 가져온다
``` ```
만약 한 컨테이너가 존재하지 않는 ConfigMap 을 reference 한다면 해당 컨테이너는 시작되지 않는다. 만약 한 컨테이너가 존재하지 않는 ConfigMap 을 reference 한다면 해당 컨테이너는 시작되지 않는다.
### 7.4.4 ConfigMap 의 모든 entry 를 환경 변수로 넘기기 ### 7.4.4 ConfigMap 의 모든 entry 를 환경 변수로 넘기기
`envFrom` 을 사용한다. (버전 1.6 이후) `envFrom` 을 사용한다. (버전 1.6 이후)
```yaml ```yaml
spec: spec:
containers: containers:
- image: some-image - image: some-image
envFrom: envFrom:
- prefix: CONFIG_ # 모든 환경 변수가 CONFIG_ 라는 prefix 를 갖는다 - prefix: CONFIG_ # 모든 환경 변수가 CONFIG_ 라는 prefix 를 갖는다
configMapRef: configMapRef:
name: my-config-map name: my-config-map
``` ```
올바르지 않은 key 값이 있다면 무시된다. 올바르지 않은 key 값이 있다면 무시된다.
### 7.4.5 ConfigMap entry 를 command-line argument 로 넘기기 ### 7.4.5 ConfigMap entry 를 command-line argument 로 넘기기
ConfigMap 의 entry 를 직접 reference 해서 argument 로 사용할 수는 없고 (`args` 필드), 환경 변수를 생성한 뒤 해당 값을 reference 하는 방식으로 해야한다. ConfigMap 의 entry 를 직접 reference 해서 argument 로 사용할 수는 없고 (`args` 필드), 환경 변수를 생성한 뒤 해당 값을 reference 하는 방식으로 해야한다.
(생성하고 reference 하기!) (생성하고 reference 하기!)
### 7.4.6 `configMap` volume 을 사용하여 ConfigMap entry 를 file 로 사용하기 ### 7.4.6 `configMap` volume 을 사용하여 ConfigMap entry 를 file 로 사용하기
보통 환경 변수나 argument 로 넘길 때에는 짧은 값을 사용하는데, (그런가...) ConfigMap 의 경우 파일 전체가 들어갈 수도 있다. 이 파일을 컨테이너가 사용할 수 있도록 하려면 `configMap` volume 을 사용해야한다. 보통 환경 변수나 argument 로 넘길 때에는 짧은 값을 사용하는데, (그런가...) ConfigMap 의 경우 파일 전체가 들어갈 수도 있다. 이 파일을 컨테이너가 사용할 수 있도록 하려면 `configMap` volume 을 사용해야한다.
`configMap` volume 을 사용하면 ConfigMap 의 모든 entry 가 파일로 공개된다. 컨테이너 내부의 프로세스는 해당 파일을 읽어와서 설정 값을 사용할 수 있다. `configMap` volume 을 사용하면 ConfigMap 의 모든 entry 가 파일로 공개된다. 컨테이너 내부의 프로세스는 해당 파일을 읽어와서 설정 값을 사용할 수 있다.
#### Volume 에 ConfigMap entry 넣기 #### Volume 에 ConfigMap entry 넣기
```yaml ```yaml
spec: spec:
containers: containers:
- image: nginx:alpine - image: nginx:alpine
name: web-server name: web-server
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /etc/nginx/conf.d mountPath: /etc/nginx/conf.d
readOnly: true readOnly: true
volumes: volumes:
- name: config - name: config
configMap: configMap:
name: fortune-config name: fortune-config
``` ```
위와 같이 해주면 `fortune-config` ConfigMap 에 있는 파일 전체를 `mountPath` 에 mount 해준다. 위와 같이 해주면 `fortune-config` ConfigMap 에 있는 파일 전체를 `mountPath` 에 mount 해준다.
더불어 파일 전체가 아니라 일부만 mount 할 수도 있다. (ConfigMap 을 두개 만들어서 따로 마운트 하는 것은 불편하지 않을까) 더불어 파일 전체가 아니라 일부만 mount 할 수도 있다. (ConfigMap 을 두개 만들어서 따로 마운트 하는 것은 불편하지 않을까)
```yaml ```yaml
volumes: volumes:
- name: config - name: config
configMap: configMap:
name: fortune-config name: fortune-config
items: items:
- key: <KEY> - key: <KEY>
path: <PATH> path: <PATH>
``` ```
위와 같이 하면 `<KEY>` 에 해당되는 값을 volume 의 `<PATH>` 에 저장한다. 주의할 점은 `path` 를 각각 입력해야한다는 점이다. 위와 같이 하면 `<KEY>` 에 해당되는 값을 volume 의 `<PATH>` 에 저장한다. 주의할 점은 `path` 를 각각 입력해야한다는 점이다.
추가로, volume 을 사용할 때 디렉토리로 mount 하게 되면 컨테이너의 해당 디렉토리에 아무 파일도 없을 경우 문제가 되지 않지만, 파일이 존재했다면 해당 파일은 더 이상 사용할 수 없고 mount 한 볼륨의 내용으로 대체된다. 그래서 만약 `/etc` 와 같은 경로에 파일을 mount 하고 싶다면 컨테이너 자체의 `/etc` 가 날아가버리기 때문에 mount 하기 어렵다. 추가로, volume 을 사용할 때 디렉토리로 mount 하게 되면 컨테이너의 해당 디렉토리에 아무 파일도 없을 경우 문제가 되지 않지만, 파일이 존재했다면 해당 파일은 더 이상 사용할 수 없고 mount 한 볼륨의 내용으로 대체된다. 그래서 만약 `/etc` 와 같은 경로에 파일을 mount 하고 싶다면 컨테이너 자체의 `/etc` 가 날아가버리기 때문에 mount 하기 어렵다.
그래서 `subPath` 를 사용하면 volume 전체를 mount 하지 않고 한 디렉토리나 한 파일만을 mount 할 수 있다. 하지만 이렇게 하나의 파일만을 mount 하는 경우에는 파일이 업데이트 될 때 문제가 발생하는데 다음 섹션에서 알아본다. 그래서 `subPath` 를 사용하면 volume 전체를 mount 하지 않고 한 디렉토리나 한 파일만을 mount 할 수 있다. 하지만 이렇게 하나의 파일만을 mount 하는 경우에는 파일이 업데이트 될 때 문제가 발생하는데 다음 섹션에서 알아본다.
#### `configMap` volume 의 파일 권한 #### `configMap` volume 의 파일 권한
Default 644. `defaultMode` 값을 수정하여 변경할 수 있다. Default 644. `defaultMode` 값을 수정하여 변경할 수 있다.
### 7.4.7 앱 재시작 없이 설정 업데이트하기 ### 7.4.7 앱 재시작 없이 설정 업데이트하기
환경 변수나 command line argument 를 사용하면, 설정 값을 바꿀 때 앱을 재시작해야 한다. 반면 ConfigMap 을 사용하면 그럴 필요가 없어진다. 환경 변수나 command line argument 를 사용하면, 설정 값을 바꿀 때 앱을 재시작해야 한다. 반면 ConfigMap 을 사용하면 그럴 필요가 없어진다.
ConfigMap 을 수정하게 되면 ConfigMap 을 참조하고 있는 모든 volume 에서 파일이 수정된다. 그러면 앱이 파일의 변경을 감지하고 reload 하면 된다. (다만 오래 걸릴 수 있으니 주의) ConfigMap 을 수정하게 되면 ConfigMap 을 참조하고 있는 모든 volume 에서 파일이 수정된다. 그러면 앱이 파일의 변경을 감지하고 reload 하면 된다. (다만 오래 걸릴 수 있으니 주의)
#### ConfigMap 수정하기 #### ConfigMap 수정하기
`kubectl edit` 으로 수정하면 된다. `kubectl edit` 으로 수정하면 된다.
#### 파일의 업데이트는 atomic #### 파일의 업데이트는 atomic
쿠버네티스가 파일을 모두 업데이트 하기 전에 앱이 이를 감지하고 reload 하는 일은 일어나지 않는다. 쿠버네티스에서 파일의 업데이트는 atomic 하게 되어서, 업데이트가 한 번에 되고, 이는 symbolic link 를 사용해서 한다. 쿠버네티스가 파일을 모두 업데이트 하기 전에 앱이 이를 감지하고 reload 하는 일은 일어나지 않는다. 쿠버네티스에서 파일의 업데이트는 atomic 하게 되어서, 업데이트가 한 번에 되고, 이는 symbolic link 를 사용해서 한다.
파일이 수정되면 수정 사항이 반영된 파일들을 만들어 한 디렉토리에 집어넣은 뒤 symbolic link 를 수정하여 해당 디렉토리 안을 참조하도록 한다. 파일이 수정되면 수정 사항이 반영된 파일들을 만들어 한 디렉토리에 집어넣은 뒤 symbolic link 를 수정하여 해당 디렉토리 안을 참조하도록 한다.
#### 파일 하나만 mount 하면 업데이트 안 됨 #### 파일 하나만 mount 하면 업데이트 안 됨
(책이 집필될 시점에서는) Volume 전체가 아니라 한 파일만 mount 하는 경우 업데이트가 되지 않는다. 그래서 대안으로는 volume 전체를 다른 디렉토리에 마운트 한 뒤 필요한 위치의 파일에서 참조하도록 symbolic link 를 만들어주는 방법이 있다. (책이 집필될 시점에서는) Volume 전체가 아니라 한 파일만 mount 하는 경우 업데이트가 되지 않는다. 그래서 대안으로는 volume 전체를 다른 디렉토리에 마운트 한 뒤 필요한 위치의 파일에서 참조하도록 symbolic link 를 만들어주는 방법이 있다.
#### ConfigMap 업데이트시 주의할 점 #### ConfigMap 업데이트시 주의할 점
ConfigMap 을 업데이트하는데 앱이 만약 설정이 변경된 것을 reload 하지 않는다면, pod 은 얼마든지 재시작 될 수 있으므로 일부 pod 는 수정되기 전의 설정을 그대로 가지고 있을 수도 있다. ConfigMap 을 업데이트하는데 앱이 만약 설정이 변경된 것을 reload 하지 않는다면, pod 은 얼마든지 재시작 될 수 있으므로 일부 pod 는 수정되기 전의 설정을 그대로 가지고 있을 수도 있다.
그러므로 앱 자체에 reload 기능이 없다면, ConfigMap 을 수정하는 것은 좋은 방법이 아닐 수 있다. 그러므로 앱 자체에 reload 기능이 없다면, ConfigMap 을 수정하는 것은 좋은 방법이 아닐 수 있다.
만약 reload 기능이 있다면 ConfigMap 을 수정해도 괜찮으나 존재하는 instance 들 상에서 파일이 동시에 업데이트 되지 않을 수도 있으므로 잠시 sync 가 맞지 않을 수 있다는 점에 유의해야 한다. 만약 reload 기능이 있다면 ConfigMap 을 수정해도 괜찮으나 존재하는 instance 들 상에서 파일이 동시에 업데이트 되지 않을 수도 있으므로 잠시 sync 가 맞지 않을 수 있다는 점에 유의해야 한다.
(??? atomic 하게 된다고 했던 것 같은데...) (??? atomic 하게 된다고 했던 것 같은데...)
## 7.5 Secrets for sensitive data ## 7.5 Secrets for sensitive data
--- ---
Credential 의 경우 안전하게 전달되어야 한다. Credential 의 경우 안전하게 전달되어야 한다.
### 7.5.1 Secrets ### 7.5.1 Secrets
ConfigMap 과 비슷하다. 컨테이너에 환경 변수를 전달할 수도 있고, volume 을 통해 Secret 의 파일을 사용할 수도 있다. ConfigMap 과 비슷하다. 컨테이너에 환경 변수를 전달할 수도 있고, volume 을 통해 Secret 의 파일을 사용할 수도 있다.
쿠버네티스는 Secret 에 접근할 필요가 있는 pod 가 실행 중인 노드에서만 Secret 이 접근 가능하도록 한다. 그리고 Secret 은 메모리 상에만 존재하여 절대 물리적 저장소에 저장되지 않는다. 쿠버네티스는 Secret 에 접근할 필요가 있는 pod 가 실행 중인 노드에서만 Secret 이 접근 가능하도록 한다. 그리고 Secret 은 메모리 상에만 존재하여 절대 물리적 저장소에 저장되지 않는다.
Secret 과 ConfigMap 중 고민이 될 때에는 다음 기준을 고려해본다. Secret 과 ConfigMap 중 고민이 될 때에는 다음 기준을 고려해본다.
- Non-sensitive, plain configuration data 이면 ConfigMap - Non-sensitive, plain configuration data 이면 ConfigMap
- Sensitive, 특별한 관리가 필요한 정보가 존재한다면 Secret - Sensitive, 특별한 관리가 필요한 정보가 존재한다면 Secret
### 7.5.2 Default token Secret ### 7.5.2 Default token Secret
모든 pod 에는 기본적으로 `secret` volume 이 존재한다. `describe` 를 이용해서 내부 정보를 보면 `ca.crt`, `namespace`, `token` 이 있는데 이 정보를 이용하면 Kubernetes API server 에 요청을 직접 보낼 수 있다. (필요하다면) 모든 pod 에는 기본적으로 `secret` volume 이 존재한다. `describe` 를 이용해서 내부 정보를 보면 `ca.crt`, `namespace`, `token` 이 있는데 이 정보를 이용하면 Kubernetes API server 에 요청을 직접 보낼 수 있다. (필요하다면)
### 7.5.3 Secret 생성 ### 7.5.3 Secret 생성
`kubectl create secret generic <NAME> --from-file=<FILE>` `kubectl create secret generic <NAME> --from-file=<FILE>`
설정 방법 자체는 ConfigMap 과 유사하다. 설정 방법 자체는 ConfigMap 과 유사하다.
### 7.5.4 ConfigMap 과 Secret 차이 ### 7.5.4 ConfigMap 과 Secret 차이
각 리소스를 생성하고 YAML output 을 비교해 보면 다르다. Secret 의 entry 들은 Base64 로 인코딩된 문자열이다. 반면 ConfigMap 의 경우 평문이 그대로 출력된다. 각 리소스를 생성하고 YAML output 을 비교해 보면 다르다. Secret 의 entry 들은 Base64 로 인코딩된 문자열이다. 반면 ConfigMap 의 경우 평문이 그대로 출력된다.
추가로 Secrets 의 경우에는 binary data 를 갖고 있을 수 있다. Base64 로 binary data 를 인코딩해서 넣을 수 있는 것이다. 추가로 Secrets 의 경우에는 binary data 를 갖고 있을 수 있다. Base64 로 binary data 를 인코딩해서 넣을 수 있는 것이다.
만약 non-binary data 라면 `stringData` 필드를 이용해서 평문을 그대로 저장할 수도 있다. 만약 non-binary data 라면 `stringData` 필드를 이용해서 평문을 그대로 저장할 수도 있다.
Secret 을 컨테이너에서 읽게 되면 entry 는 복호화되어서 원본 값 그대로 파일에 쓰이게 된다. 환경 변수로 넘겨주는 경우에도 동일하다. 앱 자체에서 복호화 할 필요는 없다. Secret 을 컨테이너에서 읽게 되면 entry 는 복호화되어서 원본 값 그대로 파일에 쓰이게 된다. 환경 변수로 넘겨주는 경우에도 동일하다. 앱 자체에서 복호화 할 필요는 없다.
### 7.5.5 Pod 에서 Secret 사용하기 ### 7.5.5 Pod 에서 Secret 사용하기
(생략) (생략)
`secret` volume 은 in-memory filesystem (tmpfs) 을 사용하여 Secret 의 파일들을 서장한다. 디스크에 직접 쓰지 않는다. `secret` volume 은 in-memory filesystem (tmpfs) 을 사용하여 Secret 의 파일들을 서장한다. 디스크에 직접 쓰지 않는다.
#### 환경 변수로 내보내기 #### 환경 변수로 내보내기
환경 변수로 내보낼 때는 `valueFrom` 을 사용하고, ConfigMap 에서는 `configMapKeyRef` 를 사용했던 것과는 달리, `secretKeyRef` 를 사용하면 된다. 환경 변수로 내보낼 때는 `valueFrom` 을 사용하고, ConfigMap 에서는 `configMapKeyRef` 를 사용했던 것과는 달리, `secretKeyRef` 를 사용하면 된다.
환경 변수로 내보내는 것이 가능은 하지만 추천되지는 않는다. 환경 변수는 다른 어딘가 로그나 에러 리포트에 의도치 않게 저장될 수도 있다. 또 child process 가 환경 변수를 모두 상속받게 되는데 child process 가 third-party app 이면 secret data 에 무슨 일이 생길지 알 수 없다. 환경 변수로 내보내는 것이 가능은 하지만 추천되지는 않는다. 환경 변수는 다른 어딘가 로그나 에러 리포트에 의도치 않게 저장될 수도 있다. 또 child process 가 환경 변수를 모두 상속받게 되는데 child process 가 third-party app 이면 secret data 에 무슨 일이 생길지 알 수 없다.
Volume 사용이 추천된다. Volume 사용이 추천된다.
### 7.5.6 Understanding image pull Secrets ### 7.5.6 Understanding image pull Secrets
때로 쿠버네티스 자체에서 credential 을 요구하는 경우가 있다. 때로 쿠버네티스 자체에서 credential 을 요구하는 경우가 있다.
책의 예시는 private image registry 에서 pull 받는 경우인데, `docker-registry` Secret 을 만들면 `.dockercfg` 파일이 생긴다 (`docker login` 명령을 실행하면 생성되는 파일과 동일) Secret 의 이름을 이용해 `.spec.imagePullSecrets.name` 에 이름을 전달해 주면 private repo 에서 pull 받을 수 있게 된다. 책의 예시는 private image registry 에서 pull 받는 경우인데, `docker-registry` Secret 을 만들면 `.dockercfg` 파일이 생긴다 (`docker login` 명령을 실행하면 생성되는 파일과 동일) Secret 의 이름을 이용해 `.spec.imagePullSecrets.name` 에 이름을 전달해 주면 private repo 에서 pull 받을 수 있게 된다.
매번 이렇게 설정해 주기는 번거로우므로 12장에서 ServiceAccount 를 이용해 자동으로 추가하는 방법을 배운다. 매번 이렇게 설정해 주기는 번거로우므로 12장에서 ServiceAccount 를 이용해 자동으로 추가하는 방법을 배운다.
--- ---
## Discussion & Additional Topics ## Discussion & Additional Topics
### What is a valid DNS subdomain? ### What is a valid DNS subdomain?
- Alphanumeric characters, dashes, underscores and dots only. May contain optional leading dot. - Alphanumeric characters, dashes, underscores and dots only. May contain optional leading dot.
아래는 쿠버네티스 공식 문서에서 가져온 내용: 아래는 쿠버네티스 공식 문서에서 가져온 내용:
#### DNS Subdomain Names #### DNS Subdomain Names
Most resource types require a name that can be used as a DNS subdomain name as defined in RFC 1123. This means the name must: Most resource types require a name that can be used as a DNS subdomain name as defined in RFC 1123. This means the name must:
- contain no more than 253 characters - contain no more than 253 characters
- contain only lowercase alphanumeric characters, '-' or '.' - contain only lowercase alphanumeric characters, '-' or '.'
- start with an alphanumeric character - start with an alphanumeric character
- end with an alphanumeric character - end with an alphanumeric character
### Resolution of key collisions when creating ConfigMaps? ### Resolution of key collisions when creating ConfigMaps?
- 그냥 애초에 생성이 안 되는 듯 하다. - 그냥 애초에 생성이 안 되는 듯 하다.
``` ```
$ kubectl create configmap test --from-literal=some=thing2 --from-literal=some=thing $ kubectl create configmap test --from-literal=some=thing2 --from-literal=some=thing
error: cannot add key "some", another key by that name already exists in Data for ConfigMap "test" error: cannot add key "some", another key by that name already exists in Data for ConfigMap "test"
``` ```
### YAML 파일로 ConfigMap 을 만든다면 literal 을 제외한 다른 옵션은 어떻게 사용하는지? ### YAML 파일로 ConfigMap 을 만든다면 literal 을 제외한 다른 옵션은 어떻게 사용하는지?
- Literal 옵션의 경우 `key: value` 로 등록하면 되는 것이고, - Literal 옵션의 경우 `key: value` 로 등록하면 되는 것이고,
- 파일 entry 를 넣는다면 `|` 를 이용해서 아래와 같이 하면 된다. - 파일 entry 를 넣는다면 `|` 를 이용해서 아래와 같이 하면 된다.
```yaml ```yaml
data: data:
some-key: | some-key: |
property1=value1 property1=value1
property2=value2 property2=value2
property3=value3 property3=value3
property4=value4 property4=value4
``` ```
### ConfigMap 에서 파일을 사용하는 경우??? ### ConfigMap 에서 파일을 사용하는 경우???
- ConfigMap 에서 파일을 entry 로 넣는 경우 mount 해서 사용하는게 맞을 것 같아 보인다. - ConfigMap 에서 파일을 entry 로 넣는 경우 mount 해서 사용하는게 맞을 것 같아 보인다.
- 안 그러면 불러왔는데 multi-line 일테니 직접 파싱 해서 사용 (??) - 안 그러면 불러왔는데 multi-line 일테니 직접 파싱 해서 사용 (??)
- Mount 를 잘 하면 `.properties` 나 `.env` 가 들어갈 위치에 config file 을 넣어줄 수 있지 않을까? - Mount 를 잘 하면 `.properties``.env` 가 들어갈 위치에 config file 을 넣어줄 수 있지 않을까?
- 그러면 애플리케이션은 해당 설정 파일을 읽으며 자연스럽게 동작! - 그러면 애플리케이션은 해당 설정 파일을 읽으며 자연스럽게 동작!
### Mounted ConfigMaps are updated automatically ### Mounted ConfigMaps are updated automatically
(공식 문서 발췌) (공식 문서 발췌)
When a ConfigMap already being consumed in a volume is updated, *projected keys are eventually updated as well*. **Kubelet is checking whether the mounted ConfigMap is fresh on every periodic sync**. When a ConfigMap already being consumed in a volume is updated, *projected keys are eventually updated as well*. **Kubelet is checking whether the mounted ConfigMap is fresh on every periodic sync**.
However, it is using its *local ttl-based cache* for getting the current value of the ConfigMap. As a result, the total delay [from the moment when the ConfigMap is updated to the moment when new keys are projected to the pod] can be as long as kubelet sync period (1 minute by default) + ttl of ConfigMaps cache (1 minute by default) in kubelet. However, it is using its *local ttl-based cache* for getting the current value of the ConfigMap. As a result, the total delay [from the moment when the ConfigMap is updated to the moment when new keys are projected to the pod] can be as long as kubelet sync period (1 minute by default) + ttl of ConfigMaps cache (1 minute by default) in kubelet.
You can trigger an immediate refresh by updating one of the pod's annotations. You can trigger an immediate refresh by updating one of the pod's annotations.
> Note: A container using a ConfigMap as a `subPath` volume will not receive ConfigMap updates. > Note: A container using a ConfigMap as a `subPath` volume will not receive ConfigMap updates.
### Secret 종류에 generic, docker-registry 말고 뭐가 더 있나? ### Secret 종류에 generic, docker-registry 말고 뭐가 더 있나?
- [공식 문서](https://kubernetes.io/docs/concepts/configuration/secret/)에 자료가 많다! - [공식 문서](https://kubernetes.io/docs/concepts/configuration/secret/)에 자료가 많다!
| Builtin Type | Usage | | Builtin Type | Usage |
|--------------|-------| |--------------|-------|
| `Opaque` | arbitrary user-defined data | | `Opaque` | arbitrary user-defined data |
| `kubernetes.io/service-account-token` | service account token | | `kubernetes.io/service-account-token` | service account token |
| `kubernetes.io/dockercfg` | serialized `~/.dockercfg` file | | `kubernetes.io/dockercfg` | serialized `~/.dockercfg` file |
| `kubernetes.io/dockerconfigjson` | serialized `~/.docker/config.json` file | | `kubernetes.io/dockerconfigjson` | serialized `~/.docker/config.json` file |
| `kubernetes.io/basic-auth` | credentials for basic authentication | | `kubernetes.io/basic-auth` | credentials for basic authentication |
| `kubernetes.io/ssh-auth` | credentials for SSH authentication | | `kubernetes.io/ssh-auth` | credentials for SSH authentication |
| `kubernetes.io/tls` | data for a TLS client or server | | `kubernetes.io/tls` | data for a TLS client or server |
| `bootstrap.kubernetes.io/token` | bootstrap token data | | `bootstrap.kubernetes.io/token` | bootstrap token data |
### Restrictions of Secrets ### Restrictions of Secrets
- Pod 생성 전에 Secret 를 만들어야 함 - Pod 생성 전에 Secret 를 만들어야 함
- 없는 값을 참조하면 pod 가 시작하지 않는다 - 없는 값을 참조하면 pod 가 시작하지 않는다
- Secret 은 namespace 를 가지므로, 같은 namespace 안에 있어야 참조할 수 있음 - Secret 은 namespace 를 가지므로, 같은 namespace 안에 있어야 참조할 수 있음
- 각 Secret 의 최대 용량은 1MB 이다. - 각 Secret 의 최대 용량은 1MB 이다.
- API server, Kubelet 의 자원 낭비를 막기 위한 장치 - API server, Kubelet 의 자원 낭비를 막기 위한 장치
- 물론 작은 Secret 여러 개 만들 수 있는데 이것 까지도 통합적으로 용량 제한하는 기능은 계획 중 - 물론 작은 Secret 여러 개 만들 수 있는데 이것 까지도 통합적으로 용량 제한하는 기능은 계획 중
### More use cases of Secret ### More use cases of Secret
- https://kubernetes.io/docs/concepts/configuration/secret/#use-cases - https://kubernetes.io/docs/concepts/configuration/secret/#use-cases
### Hard link vs Symbolic link ### Hard link vs Symbolic link
- Hard link - Hard link
- 원본 파일과 동일한 `inode` 를 사용한다. 즉, **원본 파일을 가르키는** 링크이다. 그러므로 원본 파일이 삭제되어도 사용 가능하다. - 원본 파일과 동일한 `inode` 를 사용한다. 즉, **원본 파일을 가르키는** 링크이다. 그러므로 원본 파일이 삭제되어도 사용 가능하다.
- Symbolic link - Symbolic link
- 원본 파일의 **이름을 가르키는** 링크로, 원본 파일이 삭제되면 사용이 불가능하다. - 원본 파일의 **이름을 가르키는** 링크로, 원본 파일이 삭제되면 사용이 불가능하다.
### Where does ConfigMap data get stored? ### Where does ConfigMap data get stored?
- `etcd` - `etcd`
- https://stackoverflow.com/questions/53935597/where-does-configmap-data-gets-stored/53936061#53936061 - https://stackoverflow.com/questions/53935597/where-does-configmap-data-gets-stored/53936061#53936061
--- ---
후기: 이해는 되지만 잘 와닿지가 않는 느낌인데, 나중에 다시 읽어보겠습니다... 후기: 이해는 되지만 잘 와닿지가 않는 느낌인데, 나중에 다시 읽어보겠습니다...

View File

@@ -1,490 +1,490 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Kubernetes] categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops] tags: [kubernetes, sre, devops]
title: "09. Deployments: Updating Applications Declaratively" title: "09. Deployments: Updating Applications Declaratively"
date: "2021-04-30" date: "2021-04-30"
github_title: "2021-04-30-09-deployments" github_title: "2021-04-30-09-deployments"
image: image:
path: /assets/img/posts/k8s-09.jpeg path: /assets/img/posts/k8s-09.jpeg
--- ---
![k8s-09.jpeg](../../../assets/img/posts/k8s-09.jpeg) _Rolling update of Deployments (출처: livebook.manning.com/book/kubernetes-in-action/chapter-9)_ ![k8s-09.jpeg](../../../assets/img/posts/k8s-09.jpeg) _Rolling update of Deployments (출처: livebook.manning.com/book/kubernetes-in-action/chapter-9)_
### 주요 내용 ### 주요 내용
- Deployment 를 사용하여... - Deployment 를 사용하여...
- 무중단 배포를 하는 방법 - 무중단 배포를 하는 방법
- 롤백하는 방법 - 롤백하는 방법
## 9.1 Updating applications running in pods ## 9.1 Updating applications running in pods
--- ---
만약 서비스/앱을 업데이트하고 싶다면 2가지 방법이 있다. 만약 서비스/앱을 업데이트하고 싶다면 2가지 방법이 있다.
1. Pod 를 모두 지운 후, 업데이트 된 pod 로 새롭게 시작 1. Pod 를 모두 지운 후, 업데이트 된 pod 로 새롭게 시작
2. 새로운 pod 를 시작한 뒤 새로운 pod 가 요청을 처리할 준비가 되면 기존 pod 를 지운다. 2. 새로운 pod 를 시작한 뒤 새로운 pod 가 요청을 처리할 준비가 되면 기존 pod 를 지운다.
두 방법 모두 장단이 있다. 1번의 경우 잠시 서비스가 중단된다는 문제가 있고, 2번의 경우 배포 중에 2가지 버전의 서비스가 동시에 존재하게 되므로 이를 잘 처리하기 위해서 서비스/앱 단에서 처리해줘야 한다. (하위 호환성) 두 방법 모두 장단이 있다. 1번의 경우 잠시 서비스가 중단된다는 문제가 있고, 2번의 경우 배포 중에 2가지 버전의 서비스가 동시에 존재하게 되므로 이를 잘 처리하기 위해서 서비스/앱 단에서 처리해줘야 한다. (하위 호환성)
### 9.1.1 삭제 후 새롭게 생성 ### 9.1.1 삭제 후 새롭게 생성
ReplicationController / ReplicaSet 의 경우 pod template 을 수정할 수 있었다. ReplicationController / ReplicaSet 의 경우 pod template 을 수정할 수 있었다.
- Pod template 변경 (`kubectl apply -f ...`) - Pod template 변경 (`kubectl apply -f ...`)
- Old pod 모두 삭제 - Old pod 모두 삭제
- ReplicationController / ReplicaSet 이 삭제를 감지하고 새로운 pod template 으로 생성 - ReplicationController / ReplicaSet 이 삭제를 감지하고 새로운 pod template 으로 생성
당연히, rc/rs 가 삭제를 감지하고 pod 이 요청을 처리할 준비가 될 때까지 서비스가 중단된다. 당연히, rc/rs 가 삭제를 감지하고 pod 이 요청을 처리할 준비가 될 때까지 서비스가 중단된다.
### 9.1.2 새로운 pod 생성 후 기존 pod 삭제 ### 9.1.2 새로운 pod 생성 후 기존 pod 삭제
#### 한 번에 새로운 pod 로 교체하기 #### 한 번에 새로운 pod 로 교체하기
이 방법을 사용할 때 고려해야할 점이 있다면, 새로운 pod 를 생성한 뒤 삭제하는 것이므로 평소보다 2배 많은 pod 가 존재하게 되어 리소스를 많이 사용하게 된다. (이를 버틸 수 있는 리소스가 있어야 한다) 이 방법을 사용할 때 고려해야할 점이 있다면, 새로운 pod 를 생성한 뒤 삭제하는 것이므로 평소보다 2배 많은 pod 가 존재하게 되어 리소스를 많이 사용하게 된다. (이를 버틸 수 있는 리소스가 있어야 한다)
이 방법도 간단하다. Pod 앞에 Service 가 붙어있을 것이므로, Service 의 label selector 롤 고쳐주면 된다. 이 방법도 간단하다. Pod 앞에 Service 가 붙어있을 것이므로, Service 의 label selector 롤 고쳐주면 된다.
- 새로운 pod template 을 사용하는 ReplicaSet 생성 - 새로운 pod template 을 사용하는 ReplicaSet 생성
- ReplicaSet 이 만든 pod 들이 모두 준비되었는지 확인 - ReplicaSet 이 만든 pod 들이 모두 준비되었는지 확인
- Service 의 label selector 를 교체 (`kubectl set selector ...`) - Service 의 label selector 를 교체 (`kubectl set selector ...`)
이 방법은 *blue-green deployment* 라고 한다. 이 방법은 *blue-green deployment* 라고 한다.
#### 롤링 업데이트 #### 롤링 업데이트
Rolling update 방식에서는 pod 를 조금씩 교체한다. Rolling update 방식에서는 pod 를 조금씩 교체한다.
이를 수동으로 하는 경우에는 rc/rs 의 replica 수를 조절해야한다. 기존의 rc/rs 에서는 replica 수를 줄이고, 새로운 rc/rs 에서는 replica 수를 늘려야 한다. 이 때 Service 의 pod selector 가 새롭게 생성되는 pod 들도 포함할 수 있어야 한다. 이를 수동으로 하는 경우에는 rc/rs 의 replica 수를 조절해야한다. 기존의 rc/rs 에서는 replica 수를 줄이고, 새로운 rc/rs 에서는 replica 수를 늘려야 한다. 이 때 Service 의 pod selector 가 새롭게 생성되는 pod 들도 포함할 수 있어야 한다.
롤링 업데이트는 수동으로 하면 실수할 확률이 매우 높으므로, 자동화하는 것이 좋다. 롤링 업데이트는 수동으로 하면 실수할 확률이 매우 높으므로, 자동화하는 것이 좋다.
## 9.2 Performing an automatic rolling update with a ReplicationController ## 9.2 Performing an automatic rolling update with a ReplicationController
--- ---
> 아래에 소개되는 방법은 이제는 사용하지 않는 방법이다! > 아래에 소개되는 방법은 이제는 사용하지 않는 방법이다!
### 9.2.1 Initial version 실행하기 ### 9.2.1 Initial version 실행하기
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: ReplicationController kind: ReplicationController
metadata: metadata:
name: kubia-v1 name: kubia-v1
spec: spec:
replicas: 3 replicas: 3
template: template:
metadata: metadata:
name: kubia name: kubia
labels: labels:
app: kubia app: kubia
spec: spec:
containers: containers:
- image: luksa/kubia:v1 - image: luksa/kubia:v1
name: nodejs name: nodejs
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: kubia name: kubia
spec: spec:
type: NodePort # minikube 인 관계로 NodePort Service 이용 type: NodePort # minikube 인 관계로 NodePort Service 이용
selector: selector:
app: kubia app: kubia
ports: ports:
- port: 80 - port: 80
targetPort: 8080 targetPort: 8080
nodePort: 30123 nodePort: 30123
``` ```
ReplicationController 는 3개의 pod 를 생성할 것이고, Service 가 해당 pod 들을 관리할 것이다. ReplicationController 는 3개의 pod 를 생성할 것이고, Service 가 해당 pod 들을 관리할 것이다.
> minikube 를 사용해 NodePort Service 를 생성하게 되면 minikube 의 IP 주소로 요청을 보내야 한다. `minikube ip` 로 확인하면 된다. 혹은 `minikube service list` 를 입력하면 포트까지 포함된 URL 이 출력된다. > minikube 를 사용해 NodePort Service 를 생성하게 되면 minikube 의 IP 주소로 요청을 보내야 한다. `minikube ip` 로 확인하면 된다. 혹은 `minikube service list` 를 입력하면 포트까지 포함된 URL 이 출력된다.
### 9.2.2 `kubectl` 로 rolling update 하기 ### 9.2.2 `kubectl` 로 rolling update 하기
> Note. 현재 `kubectl` 에 존재하지 않는 명령어이다. > Note. 현재 `kubectl` 에 존재하지 않는 명령어이다.
```bash ```bash
$ kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2 $ kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
``` ```
ReplicationController `kubia-v1` 을 `kubia-v2` 로 업데이트하고, 이미지를 `luksa/kubia:v2` 를 사용하겠다는 의미이다. 명령을 실행하면 ReplicationController `kubia-v2` 가 생성되고 롤링 업데이트가 진행된다. ReplicationController `kubia-v1``kubia-v2` 로 업데이트하고, 이미지를 `luksa/kubia:v2` 를 사용하겠다는 의미이다. 명령을 실행하면 ReplicationController `kubia-v2` 가 생성되고 롤링 업데이트가 진행된다.
#### 동작 과정 #### 동작 과정
`kubectl` 은 `kubia-v1` ReplicationController 의 pod template 을 그대로 가져와서 image 만 바꿔치기한 후 `kubia-v2` 를 만든다. `kubectl``kubia-v1` ReplicationController 의 pod template 을 그대로 가져와서 image 만 바꿔치기한 후 `kubia-v2` 를 만든다.
또한 label 에 `deployment=...` 이 추가된 것을 확인할 수 있다. 만약 label 이 변경되지 않는다면 같은 label selector 를 갖는 ReplicationController 가 2개 존재하게 될 것이므로 문제가 생긴다. (새로 생긴 rc 가 동작할 필요가 없어진다) 또한 label 에 `deployment=...` 이 추가된 것을 확인할 수 있다. 만약 label 이 변경되지 않는다면 같은 label selector 를 갖는 ReplicationController 가 2개 존재하게 될 것이므로 문제가 생긴다. (새로 생긴 rc 가 동작할 필요가 없어진다)
그래서 `kubectl` 은 ReplicationController 에 label selector 를 추가하고, rc 가 관리하고 있던 pod 의 label 에 `deployment=...` 을 추가해준다. 그래서 `kubectl` 은 ReplicationController 에 label selector 를 추가하고, rc 가 관리하고 있던 pod 의 label 에 `deployment=...` 을 추가해준다.
Label 변경이 끝나면 본격적으로 scaling 이 시작되고, pod 는 하나씩 교체된다. (rc 의 replica 수를 하나씩 감소/증가 시키는 것이다) Label 변경이 끝나면 본격적으로 scaling 이 시작되고, pod 는 하나씩 교체된다. (rc 의 replica 수를 하나씩 감소/증가 시키는 것이다)
모든 pod 의 업데이트가 끝나면 기존의 rc 는 삭제된다. 모든 pod 의 업데이트가 끝나면 기존의 rc 는 삭제된다.
> label 도 원래대로 돌아오나 조금 궁금하긴 한데, 존재하지 않는 명령이라 확인이 불가능하다. > label 도 원래대로 돌아오나 조금 궁금하긴 한데, 존재하지 않는 명령이라 확인이 불가능하다.
### 9.2.3 `kubectl rolling-update` is now obsolete ### 9.2.3 `kubectl rolling-update` is now obsolete
- 사용자가 생성한 object 들을 Kubernetes 가 직접 수정하는 것이 좋지 않다. - 사용자가 생성한 object 들을 Kubernetes 가 직접 수정하는 것이 좋지 않다.
- 위에서 `deployment=...` label 이 추가되는 것처럼 - 위에서 `deployment=...` label 이 추가되는 것처럼
- `kubectl` *client* 가 모든 작업을 수행한다. - `kubectl` *client* 가 모든 작업을 수행한다.
- `kubectl rolling-update ... --v 6` 을 하면 로그를 볼 수 있다. - `kubectl rolling-update ... --v 6` 을 하면 로그를 볼 수 있다.
- 로그를 보면 `kubectl` 이 API 서버에 보내는 요청을 하나씩 확인할 수 있다. - 로그를 보면 `kubectl` 이 API 서버에 보내는 요청을 하나씩 확인할 수 있다.
- Client 가 작업을 수행하게 되면 중간에 네트워크 연결이 유실되면 업데이트가 멈춰버린다. - Client 가 작업을 수행하게 되면 중간에 네트워크 연결이 유실되면 업데이트가 멈춰버린다.
- 이 방법은 imperative 하다. - 이 방법은 imperative 하다.
- **Kubernetes 를 사용할 때는 어떤 목표 도달 상태에 대해서 알려만 주고, 그것을 Kubernetes 가 알아서 수행하도록 해야한다.** - **Kubernetes 를 사용할 때는 어떤 목표 도달 상태에 대해서 알려만 주고, 그것을 Kubernetes 가 알아서 수행하도록 해야한다.**
- 반면 `rolling-update` 과정에서는 replica 수를 하나씩 조절한다. (좋지 않음) - 반면 `rolling-update` 과정에서는 replica 수를 하나씩 조절한다. (좋지 않음)
> `--v 6` 하면 로깅 레벨을 높여서 `kubectl` 이 API 서버에 보내는 요청을 볼 수 있다. > `--v 6` 하면 로깅 레벨을 높여서 `kubectl` 이 API 서버에 보내는 요청을 볼 수 있다.
## 9.3 Using Deployments for updating apps declaratively ## 9.3 Using Deployments for updating apps declaratively
--- ---
> Declarative 와 imperative 가 서로 상반되는 개념인듯 하다. > Declarative 와 imperative 가 서로 상반되는 개념인듯 하다.
앱 배포 및 업데이트 위해 사용하는 object 이다. Deployment 를 생성하게 되면 내부적으로 ReplicaSet 을 자동으로 생성하며, ReplicaSet 이 pod 들을 관리해주게 된다. 앱 배포 및 업데이트 위해 사용하는 object 이다. Deployment 를 생성하게 되면 내부적으로 ReplicaSet 을 자동으로 생성하며, ReplicaSet 이 pod 들을 관리해주게 된다.
> ReplicationController 는 쓰지 말라고 공식 문서에 나와있다. > ReplicationController 는 쓰지 말라고 공식 문서에 나와있다.
Deployment 를 사용하면 rc/rs 와 같은 lower-level construct 들을 사용하지 않고도 업데이트를 쉽게 할 수 있게 된다. Deployment 에 목표 도달 상태를 정해주기만 하면 나머지는 Kubernetes 가 알아서 한다. Deployment 를 사용하면 rc/rs 와 같은 lower-level construct 들을 사용하지 않고도 업데이트를 쉽게 할 수 있게 된다. Deployment 에 목표 도달 상태를 정해주기만 하면 나머지는 Kubernetes 가 알아서 한다.
### 9.3.1 Deployment 생성 ### 9.3.1 Deployment 생성
ReplicationController 와 상당히 비슷하다. Deployment 에도 label selector, replica count, pod template 가 있으며, 추가로 배포 전략을 정해주는 strategy 필드를 갖고있다. ReplicationController 와 상당히 비슷하다. Deployment 에도 label selector, replica count, pod template 가 있으며, 추가로 배포 전략을 정해주는 strategy 필드를 갖고있다.
```yaml ```yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: kubia # 이름에 버전 정보가 들어갈 필요없다! name: kubia # 이름에 버전 정보가 들어갈 필요없다!
spec: spec:
replicas: 3 replicas: 3
template: template:
metadata: metadata:
name: kubia name: kubia
labels: labels:
app: kubia app: kubia
spec: spec:
containers: containers:
- image: luksa/kubia:v1 - image: luksa/kubia:v1
name: nodejs name: nodejs
selector: selector:
matchLabels: matchLabels:
app: kubia app: kubia
``` ```
생성할 때 `--record` 옵션을 줘서 revision history 에 명령을 저장할 수 있도록 한다. 생성할 때 `--record` 옵션을 줘서 revision history 에 명령을 저장할 수 있도록 한다.
#### Deployment rollout #### Deployment rollout
`kubectl get deployment`, `kubectl describe deployment` 로 Deployment 의 상태를 확인할 수도 있지만, `rollout` 을 사용해서 확인하는 방법이 있다. (이 목적으로 만들어졌다) `kubectl get deployment`, `kubectl describe deployment` 로 Deployment 의 상태를 확인할 수도 있지만, `rollout` 을 사용해서 확인하는 방법이 있다. (이 목적으로 만들어졌다)
``` ```
$ kubectl rollout status deployment kubia $ kubectl rollout status deployment kubia
deployment "kubia" successfully rolled out deployment "kubia" successfully rolled out
``` ```
#### 생성된 pod, ReplicaSet 확인 #### 생성된 pod, ReplicaSet 확인
`kubectl get po` 로 만들어진 pod 들을 확인할 수 있고, `kubectl get rs` 를 해보면 ReplicaSet 이 생겼다는 것을 알 수 있다. `kubectl get po` 로 만들어진 pod 들을 확인할 수 있고, `kubectl get rs` 를 해보면 ReplicaSet 이 생겼다는 것을 알 수 있다.
그리고 ReplicaSet 의 이름에는 pod template 의 hash 값이 들어가 있는데, 이렇게 하는 이유는 Deployment 가 직접 pod 를 제어하지 않고 ReplicaSet 이 하기 때문이다. Deployment 는 ReplicaSet 을 여러 개 만들기도 하는데, 이때 이미 존재하는 ReplicaSet 을 추가로 만들지 않고 그대로 사용할 수 있게 된다. (pod template 이 같다면 hash 값이 일치하므로 재사용이 가능하다.) 그리고 ReplicaSet 의 이름에는 pod template 의 hash 값이 들어가 있는데, 이렇게 하는 이유는 Deployment 가 직접 pod 를 제어하지 않고 ReplicaSet 이 하기 때문이다. Deployment 는 ReplicaSet 을 여러 개 만들기도 하는데, 이때 이미 존재하는 ReplicaSet 을 추가로 만들지 않고 그대로 사용할 수 있게 된다. (pod template 이 같다면 hash 값이 일치하므로 재사용이 가능하다.)
> 앞에서 `kubectl rolling-update` 를 사용할 때는 ReplicationController 에 label 이 추가되는 부분이 있었는데, 이는 같은 label selector 를 사용하는 여러 개의 ReplicationController 생성을 막기 위한 것이었다. > 앞에서 `kubectl rolling-update` 를 사용할 때는 ReplicationController 에 label 이 추가되는 부분이 있었는데, 이는 같은 label selector 를 사용하는 여러 개의 ReplicationController 생성을 막기 위한 것이었다.
> >
> ReplicaSet 도 이 문제에서 자유롭지는 못할 것 같아서 확인해보니, label selector 에 `pod-template-hash` 가 있는 것을 확인할 수 있었다. > ReplicaSet 도 이 문제에서 자유롭지는 못할 것 같아서 확인해보니, label selector 에 `pod-template-hash` 가 있는 것을 확인할 수 있었다.
> ``` > ```
> $ kubectl get po --show-labels > $ kubectl get po --show-labels
> NAME READY STATUS RESTARTS AGE LABELS > NAME READY STATUS RESTARTS AGE LABELS
> kubia-74967b5695-bpqfm 1/1 Running 0 17s app=kubia, pod-template-hash=74967b5695 > kubia-74967b5695-bpqfm 1/1 Running 0 17s app=kubia, pod-template-hash=74967b5695
> kubia-74967b5695-djsfb 1/1 Running 0 17s app=kubia,pod-template-hash=74967b5695 > kubia-74967b5695-djsfb 1/1 Running 0 17s app=kubia,pod-template-hash=74967b5695
> kubia-74967b5695-h7d2b 1/1 Running 0 17s app=kubia,pod-template-hash=74967b5695 > kubia-74967b5695-h7d2b 1/1 Running 0 17s app=kubia,pod-template-hash=74967b5695
> ``` > ```
> 실제로 pod template hash 값을 사용하나보다. > 실제로 pod template hash 값을 사용하나보다.
#### Service 생성 #### Service 생성
Service 를 생성하여 Deployment 가 생성한 pod 에 연결할 수 있도록 해준다. Service 를 생성하여 Deployment 가 생성한 pod 에 연결할 수 있도록 해준다.
> Service 랑 Deployment 는 따로 띄워야 하나보다. > Service 랑 Deployment 는 따로 띄워야 하나보다.
### 9.3.2 Updating a Deployment ### 9.3.2 Updating a Deployment
ReplicationController 를 사용할 때보다 훨씬 간단해졌다. YAML 에서 pod template 만 고쳐주면 나머지는 Kubernetes 가 알아서 해준다! ReplicationController 를 사용할 때보다 훨씬 간단해졌다. YAML 에서 pod template 만 고쳐주면 나머지는 Kubernetes 가 알아서 해준다!
> ReplicationController 를 사용할 때는 새로운 rc 의 이름을 정해줘야 하고, 중간에 문제가 생기지는 않았는지 `kubectl rolling-update` 명령이 끝날 때까지 기다려야 했다. > ReplicationController 를 사용할 때는 새로운 rc 의 이름을 정해줘야 하고, 중간에 문제가 생기지는 않았는지 `kubectl rolling-update` 명령이 끝날 때까지 기다려야 했다.
#### Deployment strategies #### Deployment strategies
배포 전략에는 2가지가 있다. 하나는 rolling update 방식이고 이 값이 기본값이다. 나머지 하나는 `Recreate` 로, 9.1.1 에서 다룬 것과 같이 pod 를 모두 삭제한 뒤 새로운 pod 를 띄우는 방식이다. 배포 전략에는 2가지가 있다. 하나는 rolling update 방식이고 이 값이 기본값이다. 나머지 하나는 `Recreate` 로, 9.1.1 에서 다룬 것과 같이 pod 를 모두 삭제한 뒤 새로운 pod 를 띄우는 방식이다.
> 만약 다른 버전의 앱이 여러 개 동작하고 있는 것이 서비스 구조상 불가능하다면 `Recreate` 를 사용할 수 있을 것이다. 서비스가 잠시 중단되는 것은 마찬가지이다. > 만약 다른 버전의 앱이 여러 개 동작하고 있는 것이 서비스 구조상 불가능하다면 `Recreate` 를 사용할 수 있을 것이다. 서비스가 잠시 중단되는 것은 마찬가지이다.
#### 실습을 위해 rolling update 천천히 하도록 설정 #### 실습을 위해 rolling update 천천히 하도록 설정
'Rolling' 이 잘 되고있는지 확인하기 위해 `minReadySeconds` 필드를 Deployment 에 추가한다. 필드 하나를 추가하는 것이므로 `kubectl patch` 를 사용한다. 'Rolling' 이 잘 되고있는지 확인하기 위해 `minReadySeconds` 필드를 Deployment 에 추가한다. 필드 하나를 추가하는 것이므로 `kubectl patch` 를 사용한다.
```bash ```bash
$ kubectl patch deployment kubia -p '{"spec": {"minReadySeconds": 10}}' $ kubectl patch deployment kubia -p '{"spec": {"minReadySeconds": 10}}'
``` ```
> `minReadySeconds` 는 해당 pod 가 available 상태라고 판단하기 위한 최소한의 `READY` 시간이다. 이 때, 설정한 시간만큼 해당 pod 의 어떠한 컨테이너도 crash 하지 않고 살아있어야 한다. > `minReadySeconds` 는 해당 pod 가 available 상태라고 판단하기 위한 최소한의 `READY` 시간이다. 이 때, 설정한 시간만큼 해당 pod 의 어떠한 컨테이너도 crash 하지 않고 살아있어야 한다.
#### Rolling update #### Rolling update
이제 pod 에서 사용하는 이미지를 `luksa/kubia:v2` 로 변경하면 된다. `kubectl patch` 로도 할 수 있지만, `kubectl set image` 를 사용할 것이다. 이제 pod 에서 사용하는 이미지를 `luksa/kubia:v2` 로 변경하면 된다. `kubectl patch` 로도 할 수 있지만, `kubectl set image` 를 사용할 것이다.
```bash ```bash
$ kubectl set image deployment kubia nodejs=luksa/kubia:v2 $ kubectl set image deployment kubia nodejs=luksa/kubia:v2
``` ```
`kubia` 이름을 가진 Deployment 의 `nodejs` 컨테이너가 사용하는 이미지를 `luksa/kubia:v2` 로 변경하라는 의미이다. 실행하면 Deployment 의 pod template 이 변경된다. `kubia` 이름을 가진 Deployment 의 `nodejs` 컨테이너가 사용하는 이미지를 `luksa/kubia:v2` 로 변경하라는 의미이다. 실행하면 Deployment 의 pod template 이 변경된다.
변경을 감지한 Kubernetes 가 rolling update 를 알아서 해준다. 변경을 감지한 Kubernetes 가 rolling update 를 알아서 해준다.
#### Deployment 의 편리함 #### Deployment 의 편리함
필드 하나를 바꿨는데 rolling update 가 된다! 이 작업은 `kubectl` client 가 한것이 아니고 Kubernetes control plane 에서 처리한 것이다. 필드 하나를 바꿨는데 rolling update 가 된다! 이 작업은 `kubectl` client 가 한것이 아니고 Kubernetes control plane 에서 처리한 것이다.
기존 pod 를 하나씩 지우고 새로운 pod 를 하나씩 생성하는 전체적인 과정은 `rolling-update` 명령과 동일하다. 반면 업데이트가 끝난 다음에도 ReplicaSet 이 남아있게 되는데, 사용자는 Deployment 를 생성했지 ReplicaSet 을 직접 생성한 것이 아니므로 일단 고려하지 않아도 된다. 기존 pod 를 하나씩 지우고 새로운 pod 를 하나씩 생성하는 전체적인 과정은 `rolling-update` 명령과 동일하다. 반면 업데이트가 끝난 다음에도 ReplicaSet 이 남아있게 되는데, 사용자는 Deployment 를 생성했지 ReplicaSet 을 직접 생성한 것이 아니므로 일단 고려하지 않아도 된다.
오히려 ReplicaSet 이 남아있기 때문에 이를 활용할 수 있게 된다. 오히려 ReplicaSet 이 남아있기 때문에 이를 활용할 수 있게 된다.
### 9.3.3 Deployment 롤백하기 ### 9.3.3 Deployment 롤백하기
의도적으로 버그가 있는 버전을 배포해본다. Pod template 의 이미지를 변경한 후, `kubectl rollout` 명령을 이용해 rolling update 과정을 확인할 수 있다. 의도적으로 버그가 있는 버전을 배포해본다. Pod template 의 이미지를 변경한 후, `kubectl rollout` 명령을 이용해 rolling update 과정을 확인할 수 있다.
``` ```
kubectl rollout status deployment kubia kubectl rollout status deployment kubia
Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "kubia" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "kubia" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "kubia" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "kubia" rollout to finish: 1 old replicas are pending termination...
deployment "kubia" successfully rolled out deployment "kubia" successfully rolled out
``` ```
`curl` 을 때려보면 에러가 발생하는 것을 확인할 수 있다. `curl` 을 때려보면 에러가 발생하는 것을 확인할 수 있다.
롤백을 위해서는 다음과 같이 마지막 rollout 을 취소하면 된다. 롤백을 위해서는 다음과 같이 마지막 rollout 을 취소하면 된다.
``` ```
$ kubectl rollout undo deployment kubia $ kubectl rollout undo deployment kubia
deployment.apps/kubia rolled back deployment.apps/kubia rolled back
``` ```
> `undo` 명령은 rollout 이 진행 중인 경우에도 사용할 수 있다. 새로 생성된 pod 들은 삭제되고 삭제되었던 pod 들은 다시 생성된다. > `undo` 명령은 rollout 이 진행 중인 경우에도 사용할 수 있다. 새로 생성된 pod 들은 삭제되고 삭제되었던 pod 들은 다시 생성된다.
#### Rollout history 확인하기 #### Rollout history 확인하기
롤백이 가능한 이유는 history 가 저장되고 있기 때문이다. ReplicaSet 안에 revision history 가 저장되고 있다. Rollout 이 완료되었을 때 ReplicaSet 이 삭제되지 않으므로, 임의의 revision 으로 롤백할 수 있게 된다. 롤백이 가능한 이유는 history 가 저장되고 있기 때문이다. ReplicaSet 안에 revision history 가 저장되고 있다. Rollout 이 완료되었을 때 ReplicaSet 이 삭제되지 않으므로, 임의의 revision 으로 롤백할 수 있게 된다.
History 확인은 `kubectl rollout history deployment <NAME>` 으로 한다. History 확인은 `kubectl rollout history deployment <NAME>` 으로 한다.
``` ```
$ kubectl rollout history deployment kubia $ kubectl rollout history deployment kubia
deployment.apps/kubia deployment.apps/kubia
REVISION CHANGE-CAUSE REVISION CHANGE-CAUSE
1 kubectl create --filename=kubia-deployment-v1.yaml --record=true 1 kubectl create --filename=kubia-deployment-v1.yaml --record=true
3 kubectl create --filename=kubia-deployment-v1.yaml --record=true 3 kubectl create --filename=kubia-deployment-v1.yaml --record=true
4 kubectl create --filename=kubia-deployment-v1.yaml --record=true 4 kubectl create --filename=kubia-deployment-v1.yaml --record=true
``` ```
특정 revision 으로 롤백하고 싶다면, 특정 revision 으로 롤백하고 싶다면,
```bash ```bash
$ kubectl rollout undo deployment kubia --to-revision=1 $ kubectl rollout undo deployment kubia --to-revision=1
``` ```
과 같이 `--to-revision` 옵션을 사용하면 된다. 과 같이 `--to-revision` 옵션을 사용하면 된다.
위에서 Deployment 생성시 `--record` 명령을 사용했기 때문에 `CHANGE-CAUSE` 에 revision history 가 상세하게 남게 된다. 위에서 Deployment 생성시 `--record` 명령을 사용했기 때문에 `CHANGE-CAUSE` 에 revision history 가 상세하게 남게 된다.
> `--record` 를 사용하지 않고 Deployment 를 생성하면 history 에서 `CHANGE-CAUSE` 열이 `<none>` 으로 표시된다. > `--record` 를 사용하지 않고 Deployment 를 생성하면 history 에서 `CHANGE-CAUSE` 열이 `<none>` 으로 표시된다.
> >
> ``` > ```
> $ kubectl rollout history deployment kubia > $ kubectl rollout history deployment kubia
> deployment.apps/kubia > deployment.apps/kubia
> REVISION CHANGE-CAUSE > REVISION CHANGE-CAUSE
> 1 \ <none\> > 1 \ <none\>
> 2 \<none\> > 2 \<none\>
> ``` > ```
> >
> 또한, ReplicaSet 을 강제로 삭제하게 되면 그와 관련된 revision 도 함께 삭제된다. > 또한, ReplicaSet 을 강제로 삭제하게 되면 그와 관련된 revision 도 함께 삭제된다.
> ``` > ```
> $ kubectl rollout history deployment kubia > $ kubectl rollout history deployment kubia
> deployment.apps/kubia > deployment.apps/kubia
> REVISION CHANGE-CAUSE > REVISION CHANGE-CAUSE
> 2 \<none\> > 2 \<none\>
> ``` > ```
> >
> 그러므로 `kubectl rollout undo` 도 사용이 불가능하다. > 그러므로 `kubectl rollout undo` 도 사용이 불가능하다.
> ``` > ```
> $ kubectl rollout undo deployment kubia > $ kubectl rollout undo deployment kubia
> error: no rollout history found for deployment "kubia" > error: no rollout history found for deployment "kubia"
> ``` > ```
> 그냥 궁금해서 테스트했는데 뒤에 나와있다. ㅋㅋ > 그냥 궁금해서 테스트했는데 뒤에 나와있다. ㅋㅋ
그렇다면 revision 이 생길 때마다 ReplicaSet 이 남아있게 되므로 이는 작업할 때 매우 불편할 것이다. 그래서 `revisionHistoryLimit` 옵션을 Deployment 에 주게 되면 해당 개수 만큼만 revision history 가 남는다. 그렇다면 revision 이 생길 때마다 ReplicaSet 이 남아있게 되므로 이는 작업할 때 매우 불편할 것이다. 그래서 `revisionHistoryLimit` 옵션을 Deployment 에 주게 되면 해당 개수 만큼만 revision history 가 남는다.
> `revisionHistoryLimit` 을 0으로 설정하게 되면 ReplicaSet 이 자동으로 정리된다. 하지만 이 경우 롤백이 불가능하며, rollout 을 취소할 수 없게 된다. > `revisionHistoryLimit` 을 0으로 설정하게 되면 ReplicaSet 이 자동으로 정리된다. 하지만 이 경우 롤백이 불가능하며, rollout 을 취소할 수 없게 된다.
> Deployment 를 수정하면서 과거의 ReplicaSet 을 다시 이용하게 되면 해당 ReplicaSet 에 있던 revision 기록은 삭제된다. > Deployment 를 수정하면서 과거의 ReplicaSet 을 다시 이용하게 되면 해당 ReplicaSet 에 있던 revision 기록은 삭제된다.
### 9.3.4 Controlling the rate of the rollout ### 9.3.4 Controlling the rate of the rollout
앞에서 rolling update 를 할 때는 pod 를 하나씩 교체했다. 하지만 이것도 (rate) 설정할 수 있다. 앞에서 rolling update 를 할 때는 pod 를 하나씩 교체했다. 하지만 이것도 (rate) 설정할 수 있다.
#### `maxSurge` and `maxUnavailable` #### `maxSurge` and `maxUnavailable`
- `maxSurge`: Replica count 보다 추가로 존재할 수 있는 pod 의 개수의 최댓값이다. 기본값은 25% (replica count 의 최대 25% 만 더 생성할 수 있다) 이다. 계산시 소수점은 올린다. - `maxSurge`: Replica count 보다 추가로 존재할 수 있는 pod 의 개수의 최댓값이다. 기본값은 25% (replica count 의 최대 25% 만 더 생성할 수 있다) 이다. 계산시 소수점은 올린다.
- `maxUnavailable`: Replica count 중 동작이 중지된 pod 의 개수의 최댓값이다. 기본값은 25% (replica count 의 최대 25% 만 unavailable 일 수 있다) 이다. 계산시 소수점은 버린다. - `maxUnavailable`: Replica count 중 동작이 중지된 pod 의 개수의 최댓값이다. 기본값은 25% (replica count 의 최대 25% 만 unavailable 일 수 있다) 이다. 계산시 소수점은 버린다.
두 값 모두 백분율 값이 아닌 정수로도 설정할 수 있다. 두 값 모두 백분율 값이 아닌 정수로도 설정할 수 있다.
> 테스트 할 때 replica count 는 3이었으므로, `maxSurge` 는 ceil(3 * 0.25) = 1 이었고, `maxUnavailable` 은 floor(3 * 0.25) = 0 이었기 때문에 rolling update 시 pod 를 최대 한 개 추가로 생성할 수 있었던 것이며, 3개의 pod 는 항상 동작하고 있어야 하므로 새로운 pod 가 생성된 다음에서야 기존 pod 를 삭제할 수 있었던 것이다. > 테스트 할 때 replica count 는 3이었으므로, `maxSurge` 는 ceil(3 * 0.25) = 1 이었고, `maxUnavailable` 은 floor(3 * 0.25) = 0 이었기 때문에 rolling update 시 pod 를 최대 한 개 추가로 생성할 수 있었던 것이며, 3개의 pod 는 항상 동작하고 있어야 하므로 새로운 pod 가 생성된 다음에서야 기존 pod 를 삭제할 수 있었던 것이다.
`maxUnavailable` 이 1일 때 반드시 최대 1개만 unavailable 이라는 의미는 아니다. `Replica count - 1` 개가 available 해야한다는 의미이다. 그래서 만약 `maxSurge` 가 2 이고 replica count 가 3이라면, 최소 2개만 available 이면 되므로 rollout 시 기존 pod 를 즉시 하나 삭제하여 2개를 available 로 남기고, 새로운 pod 3개를 생성하게 된다. 생성한 즉시에는 당연히 unavailable 이므로 이 순간에는 unavailable pod 개수는 3이다. `maxUnavailable` 이 1일 때 반드시 최대 1개만 unavailable 이라는 의미는 아니다. `Replica count - 1` 개가 available 해야한다는 의미이다. 그래서 만약 `maxSurge` 가 2 이고 replica count 가 3이라면, 최소 2개만 available 이면 되므로 rollout 시 기존 pod 를 즉시 하나 삭제하여 2개를 available 로 남기고, 새로운 pod 3개를 생성하게 된다. 생성한 즉시에는 당연히 unavailable 이므로 이 순간에는 unavailable pod 개수는 3이다.
> `maxSurge`, `maxUnavailable` 은 replica count 에서 더하거나 빼는 값임을 기억하면 될 듯 하다. > `maxSurge`, `maxUnavailable` 은 replica count 에서 더하거나 빼는 값임을 기억하면 될 듯 하다.
### 9.3.5 Rollout 일시정지하기 ### 9.3.5 Rollout 일시정지하기
Rollout 하는 도중 일부 pod 에만 업데이트를 적용하고 조금 지켜본 다음 정상 작동한다면 나머지에도 적용하고 싶을 수 있다. (canary release) 이런 경우 업데이트된 내용으로 pod 를 새로 띄우거나 할 필요 없이 rollout 과정을 잠시 중단하면 된다. Rollout 하는 도중 일부 pod 에만 업데이트를 적용하고 조금 지켜본 다음 정상 작동한다면 나머지에도 적용하고 싶을 수 있다. (canary release) 이런 경우 업데이트된 내용으로 pod 를 새로 띄우거나 할 필요 없이 rollout 과정을 잠시 중단하면 된다.
``` ```
$ kubectl set image deployment kubia nodejs=luksa/kubia:v4 $ kubectl set image deployment kubia nodejs=luksa/kubia:v4
deployment.apps/kubia image updated deployment.apps/kubia image updated
$ kubectl rollout pause deployment kubia $ kubectl rollout pause deployment kubia
deployment.apps/kubia paused deployment.apps/kubia paused
$ kubectl rollout status deployment kubia $ kubectl rollout status deployment kubia
Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated...
``` ```
하나만 교체하고 나머지는 그대로인 상태에서 rollout 을 중지했다. `curl` 을 때려 확인해 보면 일부 요청은 새로운 버전의 pod 가 처리하고 있는 것을 확인할 수 있다. 하나만 교체하고 나머지는 그대로인 상태에서 rollout 을 중지했다. `curl` 을 때려 확인해 보면 일부 요청은 새로운 버전의 pod 가 처리하고 있는 것을 확인할 수 있다.
새로운 버전을 그대로 배포해도 괜찮겠다면, rollout 을 재개한다. 새로운 버전을 그대로 배포해도 괜찮겠다면, rollout 을 재개한다.
``` ```
$ kubectl rollout resume deployment kubia $ kubectl rollout resume deployment kubia
deployment.apps/kubia resumed deployment.apps/kubia resumed
``` ```
#### 일시정지의 단점 #### 일시정지의 단점
새롭게 생성된 pod 가 어떤 비율이 되었을 때 자동으로 pause 해주는 기능은 없다. 사실 그래서 canary release 를 하는 올바른 방법은 새로운 Deployment 를 생성하여 적당히 scaling 하는 것이다. 새롭게 생성된 pod 가 어떤 비율이 되었을 때 자동으로 pause 해주는 기능은 없다. 사실 그래서 canary release 를 하는 올바른 방법은 새로운 Deployment 를 생성하여 적당히 scaling 하는 것이다.
#### Pause deployment #### Pause deployment
Deployment 를 일시정지 할 수 있다. Deployment 를 일시정지한 후 필요한 설정 변경을 한 뒤에 Deployment 를 resume 하면 rollout 이 진행된다. Deployment 를 일시정지 할 수 있다. Deployment 를 일시정지한 후 필요한 설정 변경을 한 뒤에 Deployment 를 resume 하면 rollout 이 진행된다.
> 왜 필요할까 생각해 보니, 여러 개의 설정을 변경하는 경우 설정을 변경할 때마다 rollout 이 trigger 될 수 있다는 문제가 있다. 이를 막기 위해 Deployment 를 pause 해둔 뒤 여러 설정을 변경하고 resume 하면 rollout 을 한 번만 수행하여 변경된 설정을 모두 반영할 수 있게 된다. > 왜 필요할까 생각해 보니, 여러 개의 설정을 변경하는 경우 설정을 변경할 때마다 rollout 이 trigger 될 수 있다는 문제가 있다. 이를 막기 위해 Deployment 를 pause 해둔 뒤 여러 설정을 변경하고 resume 하면 rollout 을 한 번만 수행하여 변경된 설정을 모두 반영할 수 있게 된다.
### 9.3.6 Blocking rollouts of bad versions ### 9.3.6 Blocking rollouts of bad versions
`minReadySeconds` 는 비정상적인 버전을 배포하는 것을 막기 위해 사용하는 옵션이다. `minReadySeconds` 는 비정상적인 버전을 배포하는 것을 막기 위해 사용하는 옵션이다.
새로 생성된 pod 는 `READY` 상태로 `minReadySeconds` 만큼의 시간이 지나야 available 상태로 취급된다. 이 옵션이 유의미한 이유는 `maxUnavailable` 때문인데, available pod 가 일정 개수 이상 있어야 rolling update 를 할 수 있게 된다. Available pod 개수가 충족되지 않으면 rollout 이 진행될 수 없다. 새로 생성된 pod 는 `READY` 상태로 `minReadySeconds` 만큼의 시간이 지나야 available 상태로 취급된다. 이 옵션이 유의미한 이유는 `maxUnavailable` 때문인데, available pod 가 일정 개수 이상 있어야 rolling update 를 할 수 있게 된다. Available pod 개수가 충족되지 않으면 rollout 이 진행될 수 없다.
> Pod 가 `READY` 상태가 되려면 모든 컨테이너에서 readiness probe 가 성공해야 한다. > Pod 가 `READY` 상태가 되려면 모든 컨테이너에서 readiness probe 가 성공해야 한다.
실제로는 `minReadySeconds` 를 큰 값으로 설정해서, pod 가 `READY` 상태가 되어 실제 서비스 요청을 받고 나서도 오랜 시간동안 문제가 없는지 확인한 뒤 available pod 로 처리한다. 실제로는 `minReadySeconds` 를 큰 값으로 설정해서, pod 가 `READY` 상태가 되어 실제 서비스 요청을 받고 나서도 오랜 시간동안 문제가 없는지 확인한 뒤 available pod 로 처리한다.
> 큰 값으로 설정하면 규모가 큰 서비스는 rolling 엄청 오래 걸리겠다. > 큰 값으로 설정하면 규모가 큰 서비스는 rolling 엄청 오래 걸리겠다.
#### 버그가 있는 버전에 readiness probe 적용하여 rollout #### 버그가 있는 버전에 readiness probe 적용하여 rollout
이번에는 컨테이너에 `readinessProbe` 를 설정할 것이다. 이번에는 컨테이너에 `readinessProbe` 를 설정할 것이다.
```yaml ```yaml
apiVersion: apps/v1beta1 apiVersion: apps/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
name: kubia name: kubia
spec: spec:
replicas: 3 replicas: 3
minReadySeconds: 10 # READY 상태로 10초 있어야 한다 minReadySeconds: 10 # READY 상태로 10초 있어야 한다
strategy: strategy:
rollingUpdate: rollingUpdate:
maxSurge: 1 maxSurge: 1
maxUnavailable: 0 # 3개는 항상 동작 중이도록 한다 maxUnavailable: 0 # 3개는 항상 동작 중이도록 한다
type: RollingUpdate type: RollingUpdate
template: template:
metadata: metadata:
name: kubia name: kubia
labels: labels:
app: kubia app: kubia
spec: spec:
containers: containers:
- image: luksa/kubia:v3 - image: luksa/kubia:v3
name: nodejs name: nodejs
readinessProbe: readinessProbe:
periodSeconds: 1 # 1초마다 GET 요청을 보낸다 periodSeconds: 1 # 1초마다 GET 요청을 보낸다
httpGet: httpGet:
path: / path: /
port: 8080 port: 8080
``` ```
`kubectl apply -f ...` 으로 Deployment 를 업데이트한다. 그리고 rollout status 를 확인해본다. `kubectl apply -f ...` 으로 Deployment 를 업데이트한다. 그리고 rollout status 를 확인해본다.
``` ```
$ kubectl rollout status deployment kubia $ kubectl rollout status deployment kubia
Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "kubia" rollout to finish: 1 out of 3 new replicas have been updated...
``` ```
더 이상 출력이 되지 않는다. `kubectl get po` 로 확인해 보면 pod 하나가 `READY` 상태가 아닌 것을 볼 수 있다. 더 이상 출력이 되지 않는다. `kubectl get po` 로 확인해 보면 pod 하나가 `READY` 상태가 아닌 것을 볼 수 있다.
``` ```
$ kubectl get po $ kubectl get po
NAME READY STATUS RESTARTS AGE NAME READY STATUS RESTARTS AGE
kubia-86499d4c5f-pdfgt 0/1 Running 0 13s kubia-86499d4c5f-pdfgt 0/1 Running 0 13s
kubia-bcf9bb974-9425g 1/1 Running 0 7m10s kubia-bcf9bb974-9425g 1/1 Running 0 7m10s
kubia-bcf9bb974-9cq7f 1/1 Running 0 7m12s kubia-bcf9bb974-9cq7f 1/1 Running 0 7m12s
kubia-bcf9bb974-k7cxf 1/1 Running 0 7m14s kubia-bcf9bb974-k7cxf 1/1 Running 0 7m14s
``` ```
`describe po` 로 확인해 보면 readiness probe 가 실패했다고 나와있으며, `curl` 로 확인해보면 과거 버전 pod 로만 요청이 가고있음을 확인할 수 있다. `describe po` 로 확인해 보면 readiness probe 가 실패했다고 나와있으며, `curl` 로 확인해보면 과거 버전 pod 로만 요청이 가고있음을 확인할 수 있다.
당연히, available 상태가 되지 않았으므로 rollout 은 더 이상 진행될 수 없으며 (`maxUnavailable`) `READY` 상태가 아닌 pod 들은 Service Endpoints 에서 제거되기 때문에 과거 버전 pod 로만 요청이 가게 된다. 당연히, available 상태가 되지 않았으므로 rollout 은 더 이상 진행될 수 없으며 (`maxUnavailable`) `READY` 상태가 아닌 pod 들은 Service Endpoints 에서 제거되기 때문에 과거 버전 pod 로만 요청이 가게 된다.
> Service Endpoint 에서 추가/제거 되는 기준은 pod 가 `READY` 인지 아닌지 이다. 우선 `curl` 로 요청을 계속 보내고 있는 상태에서 버그가 있는 버전으로 rolling update 를 해보니 중간에 에러가 난 요청들이 보이긴 했다. > Service Endpoint 에서 추가/제거 되는 기준은 pod 가 `READY` 인지 아닌지 이다. 우선 `curl` 로 요청을 계속 보내고 있는 상태에서 버그가 있는 버전으로 rolling update 를 해보니 중간에 에러가 난 요청들이 보이긴 했다.
> >
> 4번째 요청까지는 OK 응답이 돌아오므로, readiness probe 가 이 pod 는 `READY` 라고 했을 것이다. 그러면서 Service Endpoints 에 이 pod 의 IP 가 추가된 것이며, 내가 보낸 `curl` 요청을 받을 수 있었던 것이다. > 4번째 요청까지는 OK 응답이 돌아오므로, readiness probe 가 이 pod 는 `READY` 라고 했을 것이다. 그러면서 Service Endpoints 에 이 pod 의 IP 가 추가된 것이며, 내가 보낸 `curl` 요청을 받을 수 있었던 것이다.
> >
> Readiness probe 는 주기적으로 실행되므로, 어느 시점부터 실패했을 것이므로 `READY` 상태가 아니게 되고 unavailable 상태가 지속되면서 rollout 은 중단된 것이다. > Readiness probe 는 주기적으로 실행되므로, 어느 시점부터 실패했을 것이므로 `READY` 상태가 아니게 되고 unavailable 상태가 지속되면서 rollout 은 중단된 것이다.
> >
> 결국 중간에 요청이 비정상적으로 처리되는 경우가 있었다는 것은 실제 서비스에서 일부 사용자들은 오류를 겪을 수도 있다는 것인데 이것이 불가피한 것인지 아닌지는 상황에 따라 다를 것 같다. 책에서는 모든 pod 가 잘못된 pod 로 교체되는 것에 비하면 낫다고 하는데, 이렇게 생각해도 과연 괜찮을련지 ㅋㅋ > 결국 중간에 요청이 비정상적으로 처리되는 경우가 있었다는 것은 실제 서비스에서 일부 사용자들은 오류를 겪을 수도 있다는 것인데 이것이 불가피한 것인지 아닌지는 상황에 따라 다를 것 같다. 책에서는 모든 pod 가 잘못된 pod 로 교체되는 것에 비하면 낫다고 하는데, 이렇게 생각해도 과연 괜찮을련지 ㅋㅋ
#### Rollout deadline #### Rollout deadline
Rollout 이 10분간 진전이 없으면 failed 처리된다. `kubectl describe` 로 확인해 보면 `ProgressDeadlineExceeded` condition 이 발생해있다. Rollout 이 10분간 진전이 없으면 failed 처리된다. `kubectl describe` 로 확인해 보면 `ProgressDeadlineExceeded` condition 이 발생해있다.
> `kubectl rollout status` 에서는 다음과 같이 에러가 발생한다. > `kubectl rollout status` 에서는 다음과 같이 에러가 발생한다.
> ``` > ```
> $ kubectl rollout status deployment kubia > $ kubectl rollout status deployment kubia
> error: deployment "kubia" exceeded its progress deadline > error: deployment "kubia" exceeded its progress deadline
> ``` > ```
이 값 또한 `progressDeadlineSeconds` 로 설정할 수 있다. 이 값 또한 `progressDeadlineSeconds` 로 설정할 수 있다.
#### Bad rollout 중단하기 #### Bad rollout 중단하기
`kubectl rollout undo` 를 이용해서 중단하면 된다. `kubectl rollout undo` 를 이용해서 중단하면 된다.
> Note 에는 future version 들에서 자동으로 중단될거라고 하는데, 나는 자동으로 중단되지 않았다. Unavailable pod 가 1시간 넘게 남아있었다. 수동으로 `undo` 해주니 그제서야 `Terminating` 으로 변경되었다. > Note 에는 future version 들에서 자동으로 중단될거라고 하는데, 나는 자동으로 중단되지 않았다. Unavailable pod 가 1시간 넘게 남아있었다. 수동으로 `undo` 해주니 그제서야 `Terminating` 으로 변경되었다.
--- ---
## Discussion & Additional Topics ## Discussion & Additional Topics
### BlueGreenDeployment ### BlueGreenDeployment
- https://martinfowler.com/bliki/BlueGreenDeployment.html - https://martinfowler.com/bliki/BlueGreenDeployment.html
### Canary Release ### Canary Release
- https://martinfowler.com/bliki/CanaryRelease.html - https://martinfowler.com/bliki/CanaryRelease.html
- 일부 사용자들에게만 new version 으로 서비스 하는 것 - 일부 사용자들에게만 new version 으로 서비스 하는 것

File diff suppressed because it is too large Load Diff

View File

@@ -1,106 +1,106 @@
--- ---
share: true share: true
toc: true toc: true
categories: [Development, Web] categories: [Development, Web]
tags: [development, web] tags: [development, web]
title: "블로그 이주 이야기" title: "블로그 이주 이야기"
date: "2023-06-25" date: "2023-06-25"
github_title: "2023-06-25-blog-moving" github_title: "2023-06-25-blog-moving"
image: image:
path: /assets/img/posts/blog-logo.png path: /assets/img/posts/blog-logo.png
--- ---
![blog-logo.png](../../../assets/img/posts/blog-logo.png) _New blog logo_ ![blog-logo.png](../../../assets/img/posts/blog-logo.png) _New blog logo_
오래 전, Github Pages가 불편하다는 이유로 티스토리로 옮겼었다. 오래 전, Github Pages가 불편하다는 이유로 티스토리로 옮겼었다.
근데 어쩌다 보니 결국 다시 돌아오게 되었다. 근데 어쩌다 보니 결국 다시 돌아오게 되었다.
## History ## History
2019년 12월에 `calofmijuck.github.io` 사이트를 세팅했었다. 하지만 여러 불편한 점들을 발견했고, Jekyll 문서를 열심히 읽어보고 커스터마이즈를 시도한 끝에 실패하여 결국에는 모든게 세팅되어 있고 간편하게 제공되는 티스토리로 이주했던 기억이 있다. 2019년 12월에 `calofmijuck.github.io` 사이트를 세팅했었다. 하지만 여러 불편한 점들을 발견했고, Jekyll 문서를 열심히 읽어보고 커스터마이즈를 시도한 끝에 실패하여 결국에는 모든게 세팅되어 있고 간편하게 제공되는 티스토리로 이주했던 기억이 있다.
- [github.io 사이트 설정](https://calofmijuck.tistory.com/2) - [github.io 사이트 설정](https://calofmijuck.tistory.com/2)
- [github.io 사이트 설정 - 2](https://calofmijuck.tistory.com/7) - [github.io 사이트 설정 - 2](https://calofmijuck.tistory.com/7)
- [Tistory 이주](https://calofmijuck.tistory.com/12) - [Tistory 이주](https://calofmijuck.tistory.com/12)
과거의 나에 따르면, 티스토리로 옮긴 가장 큰 이유는 카테고리 별로 잘 정리된 사이드바를 쓰고 싶었기 때문이라고 한다. Jekyll minimal mistakes 테마에서 어떻게든 해보려고 꽤나 삽질을 했던 좋지 않은 기억이 아직도 남아있다. 과거의 나에 따르면, 티스토리로 옮긴 가장 큰 이유는 카테고리 별로 잘 정리된 사이드바를 쓰고 싶었기 때문이라고 한다. Jekyll minimal mistakes 테마에서 어떻게든 해보려고 꽤나 삽질을 했던 좋지 않은 기억이 아직도 남아있다.
이번에는 지인의 소개로 [Chirpy](https://chirpy.cotes.page/)라는 깔끔한 테마를 하나 알게 되었고, [Obsidian](https://obsidian.md/)이라는 강력한 도구를 얻게 되어 다시 한 번 github.io 블로그에 도전해 보려고 한다. 이번에는 지인의 소개로 [Chirpy](https://chirpy.cotes.page/)라는 깔끔한 테마를 하나 알게 되었고, [Obsidian](https://obsidian.md/)이라는 강력한 도구를 얻게 되어 다시 한 번 github.io 블로그에 도전해 보려고 한다.
## Why Move Again? ## Why Move Again?
티스토리는 그 자체로도 충분히 좋은 플랫폼인 것이 분명하다. 특별히 아쉬운 점을 꼽아보자면, 티스토리는 그 자체로도 충분히 좋은 플랫폼인 것이 분명하다. 특별히 아쉬운 점을 꼽아보자면,
- 스킨 선택지가 (Jekyll에 비해) 다양하지 않다. 물론 내 입맛에 맞게 수정할 수는 있지만 조금 불편하다. - 스킨 선택지가 (Jekyll에 비해) 다양하지 않다. 물론 내 입맛에 맞게 수정할 수는 있지만 조금 불편하다.
- 개인적으로 기본 편집기가 매우 불편하기 때문에 마크타운이나 HTML을 사용하게 된다. - 개인적으로 기본 편집기가 매우 불편하기 때문에 마크타운이나 HTML을 사용하게 된다.
이 정도인 것 같다. Jekyll을 사용하게 되면 스킨이나 테마의 선택지 폭은 매우 넓어지게 되고, 편집은 기본적으로 마크다운(kramdown)이기 때문에 크게 불편하지 않다. 추가로 Obsidian을 붙여서 사용할 계획이므로, 글 작성을 편리하게 할 수 있을 것이라 생각했다. 그리고 수식을 기본적으로 지원하기 때문에 수학을 좋아하는 나에게는 대환영이다. 이 정도인 것 같다. Jekyll을 사용하게 되면 스킨이나 테마의 선택지 폭은 매우 넓어지게 되고, 편집은 기본적으로 마크다운(kramdown)이기 때문에 크게 불편하지 않다. 추가로 Obsidian을 붙여서 사용할 계획이므로, 글 작성을 편리하게 할 수 있을 것이라 생각했다. 그리고 수식을 기본적으로 지원하기 때문에 수학을 좋아하는 나에게는 대환영이다.
그리고 이건 개인적인 욕심인데, 작성한 글이 git과 연동이 되었으면 좋겠다고 생각한 적이 있었다. 그런데 Obsidian은 이와 같은 나의 욕심을 채워주었다. 그리고 이건 개인적인 욕심인데, 작성한 글이 git과 연동이 되었으면 좋겠다고 생각한 적이 있었다. 그런데 Obsidian은 이와 같은 나의 욕심을 채워주었다.
쓰다 보면 또 불편한 점을 발견해서 다시 티스토리로 돌아갈지도 모르겠지만, 종강도 했는데 일단은 미끼를 물어보고 세팅해서 일단 사용해 보기로 했다. 쓰다 보면 또 불편한 점을 발견해서 다시 티스토리로 돌아갈지도 모르겠지만, 종강도 했는데 일단은 미끼를 물어보고 세팅해서 일단 사용해 보기로 했다.
## Moving from Notion to Obsidian ## Moving from Notion to Obsidian
그 전에 Notion을 버리고 Obsidian으로 갈아탄 이유를 조금 적고 가야겠다. 원래 노트 앱으로 Notion을 쓰고 있었다. 학생이기 때문에 인증을 하고, 무료로 Education Plus Plan을 이용하고 있었다. Notion의 아쉬운 점도 좀 있었는데, 그 전에 Notion을 버리고 Obsidian으로 갈아탄 이유를 조금 적고 가야겠다. 원래 노트 앱으로 Notion을 쓰고 있었다. 학생이기 때문에 인증을 하고, 무료로 Education Plus Plan을 이용하고 있었다. Notion의 아쉬운 점도 좀 있었는데,
- 검색이 잘 안된다. 검색이 안되면 문서를 왜 작성하나? 위키처럼 링크를 모아둬서 찾기 편리하게 할 수도 있겠지만, 링크의 개수가 많아지면 별로 좋은 방법은 아닌 것 같다. - 검색이 잘 안된다. 검색이 안되면 문서를 왜 작성하나? 위키처럼 링크를 모아둬서 찾기 편리하게 할 수도 있겠지만, 링크의 개수가 많아지면 별로 좋은 방법은 아닌 것 같다.
- 특유의 블록 구조가 불편하다. 모든게 블럭이다. 그리고 간혹 복사 붙여넣기, 선택이 올바르게 동작하지 않는 경우가 있다. - 특유의 블록 구조가 불편하다. 모든게 블럭이다. 그리고 간혹 복사 붙여넣기, 선택이 올바르게 동작하지 않는 경우가 있다.
- 수식 입력이 불편하다. 인라인/블록 수식 모두 결국엔 블록이다. 개인적으로 수식 입력은 차라리 마크다운이 더 편한 것 같다. - 수식 입력이 불편하다. 인라인/블록 수식 모두 결국엔 블록이다. 개인적으로 수식 입력은 차라리 마크다운이 더 편한 것 같다.
- 좀 느린 것 같다. (이건 좀 억지인가?) - 좀 느린 것 같다. (이건 좀 억지인가?)
조금 다른 이야기이긴 하지만, 졸업 이후에 Notion션이 언제 Education Plus Plan을 끊어버릴지 모른다. 이와 같은 통수를 맞게 되면 무조건 마이그레이션을 해야하기 마련이다. 조금 다른 이야기이긴 하지만, 졸업 이후에 Notion션이 언제 Education Plus Plan을 끊어버릴지 모른다. 이와 같은 통수를 맞게 되면 무조건 마이그레이션을 해야하기 마련이다.
반면 Obsidian은 기본적으로 무료로 대부분의 기능을 사용할 수 있다. 유료로 제공받는 유의미한 기능은 Sync 정도인 것 같다. 이는 Obsidian 자체 클라우드에 노트를 저장하고 다른 기기와 동기화하는데 사용하는 기능인데, 굳이 Obsidian 자체 클라우드가 아니라 iCloud 등 다른 저장소를 사용할 수도 있기 때문에 Sync 기능이 큰 유입 장벽을 만드는 것 같지는 않다. 반면 Obsidian은 기본적으로 무료로 대부분의 기능을 사용할 수 있다. 유료로 제공받는 유의미한 기능은 Sync 정도인 것 같다. 이는 Obsidian 자체 클라우드에 노트를 저장하고 다른 기기와 동기화하는데 사용하는 기능인데, 굳이 Obsidian 자체 클라우드가 아니라 iCloud 등 다른 저장소를 사용할 수도 있기 때문에 Sync 기능이 큰 유입 장벽을 만드는 것 같지는 않다.
그리고 Obsidian은 기본적으로 마크다운 편집기이긴 하지만, 커스터마이즈 할 수 있는 부분이 굉장히 많고, 커뮤니티에 수많은 플러그인이 올라와 있다. 이러한 기능을 잘 사용하면 Notion 이상의 효율을 낼 수 있을 것이라고 믿었다. 그리고 Obsidian은 기본적으로 마크다운 편집기이긴 하지만, 커스터마이즈 할 수 있는 부분이 굉장히 많고, 커뮤니티에 수많은 플러그인이 올라와 있다. 이러한 기능을 잘 사용하면 Notion 이상의 효율을 낼 수 있을 것이라고 믿었다.
마지막으로 Graph View가 좀 사기인 것 같다. Reddit에서 Obsidian에 수업 필기하는 사람들의 Graph View 결과를 몇 개 봤는데, 문서 간의 수많은 연결고리가 마치 지식이 서로 연결되어 있는 느낌을 줬다. 사실 처음으로 Obsidian을 접했을 때 해보고 싶었던 것이 있었다. 배우고 있는 과목의 내용을 잘 정리해서 서로 링크로 연결하여 그 과목에서 배운 내용에 대한 지도를 만드는 것이다. 기회가 된다면 꼭 한 번 해보고 그 결과물을 Graph View로 보고 싶다. 마지막으로 Graph View가 좀 사기인 것 같다. Reddit에서 Obsidian에 수업 필기하는 사람들의 Graph View 결과를 몇 개 봤는데, 문서 간의 수많은 연결고리가 마치 지식이 서로 연결되어 있는 느낌을 줬다. 사실 처음으로 Obsidian을 접했을 때 해보고 싶었던 것이 있었다. 배우고 있는 과목의 내용을 잘 정리해서 서로 링크로 연결하여 그 과목에서 배운 내용에 대한 지도를 만드는 것이다. 기회가 된다면 꼭 한 번 해보고 그 결과물을 Graph View로 보고 싶다.
아무튼, 종합하면 Notion보다는 Obsidian이 맞겠다는 판단을 하고 과감하게 Obsidian으로 옮겼다. 아무튼, 종합하면 Notion보다는 Obsidian이 맞겠다는 판단을 하고 과감하게 Obsidian으로 옮겼다.
물론 이 결정을 한 시점은 시험기간이었음을 밝힌다.[^1] 물론 이 결정을 한 시점은 시험기간이었음을 밝힌다.[^1]
## Obsidian with Github Publisher Plugin ## Obsidian with Github Publisher Plugin
Obsidian을 Github과 연동하기 위해 [Obsidian Github Publisher](https://github.com/ObsidianPublisher/obsidian-github-publisher) 플러그인을 사용할 수 있다. Obsidian을 Github과 연동하기 위해 [Obsidian Github Publisher](https://github.com/ObsidianPublisher/obsidian-github-publisher) 플러그인을 사용할 수 있다.
![github-publisher.png](../../../assets/img/posts/github-publisher.png){: .shadow } _플러그인 설정 화면: 어느 폴더에 어떤 이름으로 파일을 업로드할지 설정할 수 있다._ ![github-publisher.png](../../../assets/img/posts/github-publisher.png){: .shadow } _플러그인 설정 화면: 어느 폴더에 어떤 이름으로 파일을 업로드할지 설정할 수 있다._
이 플러그인을 사용하면 Obsidian의 문서 중에서 `share: true` 로 마킹된 문서들을 레포에 저장할 수 있게 된다. 그렇다면 블로그 글을 Obsidian에서 작성하고, 플러그인을 이용해 레포에 push하게 되면, 자동으로 빌드/배포가 이뤄져서 블로그에 반영되는 것을 확인할 수 있을 것이다. 이 플러그인을 사용하면 Obsidian의 문서 중에서 `share: true` 로 마킹된 문서들을 레포에 저장할 수 있게 된다. 그렇다면 블로그 글을 Obsidian에서 작성하고, 플러그인을 이용해 레포에 push하게 되면, 자동으로 빌드/배포가 이뤄져서 블로그에 반영되는 것을 확인할 수 있을 것이다.
Obsidian으로 편하게 글도 쓰고, 쓴 글이 git과 연동까지 되는 일석이조의 효과를 누릴 수 있게 된다! Obsidian으로 편하게 글도 쓰고, 쓴 글이 git과 연동까지 되는 일석이조의 효과를 누릴 수 있게 된다!
## Github Pages vs Self-Hosted ## Github Pages vs Self-Hosted
이처럼 나의 욕심을 Obsidian으로 채우는 것 까지는 좋았으나, 실제로 실행에 옮기기까지 많은 고민이 있었다. 블로그가 과연 얼마나 커질지는 모르는 일이지만, 확장성을 고려했을 때 이것이 장기적으로 좋은 선택인지 생각해볼 필요가 있었기 때문이다. 이처럼 나의 욕심을 Obsidian으로 채우는 것 까지는 좋았으나, 실제로 실행에 옮기기까지 많은 고민이 있었다. 블로그가 과연 얼마나 커질지는 모르는 일이지만, 확장성을 고려했을 때 이것이 장기적으로 좋은 선택인지 생각해볼 필요가 있었기 때문이다.
Obsidian 자체는 로컬 저장소를 사용하고 있기 때문에 크게 문제가 되지는 않을 것이라 생각했다. 하지만 블로그 글을 git과 연동하게 되면서 새롭게 생각해낸(?) 문제가 하나 있었다. Obsidian 자체는 로컬 저장소를 사용하고 있기 때문에 크게 문제가 되지는 않을 것이라 생각했다. 하지만 블로그 글을 git과 연동하게 되면서 새롭게 생각해낸(?) 문제가 하나 있었다.
글을 쓰다 보면 사진을 첨부하기 마련인데, 글의 개수가 많은 상황에서도 이를 과연 git에 저장하는 것이 맞을지 고민하게 되었다! Git은 설계상 텍스트 데이터를 잘 다룬다. 하지만 이미지는 바이너리 파일이다. 용량도 보통 텍스트보다 배로 크다. 이미지를 git에 많이 저장해도 될까? 글을 쓰다 보면 사진을 첨부하기 마련인데, 글의 개수가 많은 상황에서도 이를 과연 git에 저장하는 것이 맞을지 고민하게 되었다! Git은 설계상 텍스트 데이터를 잘 다룬다. 하지만 이미지는 바이너리 파일이다. 용량도 보통 텍스트보다 배로 크다. 이미지를 git에 많이 저장해도 될까?
Git은 version control system이기 때문에, 이미지가 버전에 따라 영향을 받는다면, git에 포함되어야 함은 당연하다고 느낄 것 같다. 하지만 블로그 글에 들어가는 이미지는 자주 수정되지 않을 확률이 높기 때문에 version control의 목적으로 git을 쓴다기 보다는 단순히 저장소로 사용한다는 느낌이 좀 들었다. 그런데 자주 수정되지 않는다면 diff에서 많은 용량을 차지하지 않기 때문에 오히려 문제가 될 것도 없겠다는 생각이 들기도 했다. Git은 version control system이기 때문에, 이미지가 버전에 따라 영향을 받는다면, git에 포함되어야 함은 당연하다고 느낄 것 같다. 하지만 블로그 글에 들어가는 이미지는 자주 수정되지 않을 확률이 높기 때문에 version control의 목적으로 git을 쓴다기 보다는 단순히 저장소로 사용한다는 느낌이 좀 들었다. 그런데 자주 수정되지 않는다면 diff에서 많은 용량을 차지하지 않기 때문에 오히려 문제가 될 것도 없겠다는 생각이 들기도 했다.
이 뿐만 아니라 Github Pages 자체적으로 사용량 제한이 걸려있다. [공식 문서](https://github.com/ObsidianPublisher/obsidian-github-publisher)의 Usage Limits 섹션에 따르면, 아래와 같은 제한이 있다. (일부 후략) 이 뿐만 아니라 Github Pages 자체적으로 사용량 제한이 걸려있다. [공식 문서](https://github.com/ObsidianPublisher/obsidian-github-publisher)의 Usage Limits 섹션에 따르면, 아래와 같은 제한이 있다. (일부 후략)
- GitHub Pages source repositories have a recommended limit of 1 GB. - GitHub Pages source repositories have a recommended limit of 1 GB.
- Published GitHub Pages sites may be no larger than 1 GB. - Published GitHub Pages sites may be no larger than 1 GB.
- GitHub Pages deployments will timeout if they take longer than 10 minutes. - GitHub Pages deployments will timeout if they take longer than 10 minutes.
- GitHub Pages sites have a soft bandwidth limit of 100 GB per month. - GitHub Pages sites have a soft bandwidth limit of 100 GB per month.
- GitHub Pages sites have a soft limit of 10 builds per hour. This limit does not apply if you build and publish your site with a custom GitHub Actions workflow - GitHub Pages sites have a soft limit of 10 builds per hour. This limit does not apply if you build and publish your site with a custom GitHub Actions workflow
- In order to provide consistent quality of service for all GitHub Pages sites, rate limits may apply. - In order to provide consistent quality of service for all GitHub Pages sites, rate limits may apply.
이것 때문에 개인 서버 머신에 블로그를 배포할지 고민하기도 했고, 사진만 따로 AWS S3 + CloudFront 조합을 이용해볼까 고민하기도 했다. 요금제까지 알아봤다![^2] 이것 때문에 개인 서버 머신에 블로그를 배포할지 고민하기도 했고, 사진만 따로 AWS S3 + CloudFront 조합을 이용해볼까 고민하기도 했다. 요금제까지 알아봤다![^2]
그러나 내 머신에 배포하게 되면 자동 배포를 하기 위해 세팅할 것이 너무 많다고 판단했고, 클라우드를 사용하는 것 또한 블로그의 파일을 분산시키는 것 같아서, 결국에는 [귀여운 판다](https://tinypng.com/)의 도움을 받아 사진 파일을 압축해서 올리고 Github Pages를 이용하기로 했다. 그러나 내 머신에 배포하게 되면 자동 배포를 하기 위해 세팅할 것이 너무 많다고 판단했고, 클라우드를 사용하는 것 또한 블로그의 파일을 분산시키는 것 같아서, 결국에는 [귀여운 판다](https://tinypng.com/)의 도움을 받아 사진 파일을 압축해서 올리고 Github Pages를 이용하기로 했다.
조사했던 내용은 나중에 블로그를 확장 하게 되면 유용하게 사용할 것이다. 조사했던 내용은 나중에 블로그를 확장 하게 되면 유용하게 사용할 것이다.
## New Blog ## New Blog
이렇게 하여 Obsidian + Github Publisher + Github Pages로 새로운 블로그를 세팅해 봤다. `calofmijuck.github.io` 는 이미 교양수학 기출문제로 활용되고 있기 때문에 이 블로그는 `calofmijuck.github.io/blog` 주소가 할당되어야 했으나, custom domain을 적용하여 `blog.zxcvber.com` 이 되었다. 괜찮은 것 같다. 이렇게 하여 Obsidian + Github Publisher + Github Pages로 새로운 블로그를 세팅해 봤다. `calofmijuck.github.io` 는 이미 교양수학 기출문제로 활용되고 있기 때문에 이 블로그는 `calofmijuck.github.io/blog` 주소가 할당되어야 했으나, custom domain을 적용하여 `blog.zxcvber.com` 이 되었다. 괜찮은 것 같다.
시간될 때 댓글 기능도 붙이고, 과거 글도 몇 개 복원하고, 테마도 더 수정할 계획이다. 시간될 때 댓글 기능도 붙이고, 과거 글도 몇 개 복원하고, 테마도 더 수정할 계획이다.
[^1]: 공부 빼고 다 재미있을 시기 아니겠는가? [^1]: 공부 빼고 다 재미있을 시기 아니겠는가?
[^2]: S3는 $0.025/GB라서 부담되는 가격이 아니고, CloudFront는 매달 데이터 전송 1TB까지 무료였다. [^2]: S3는 $0.025/GB라서 부담되는 가격이 아니고, CloudFront는 매달 데이터 전송 1TB까지 무료였다.

View File

@@ -1,92 +1,92 @@
--- ---
share: true share: true
toc: true toc: true
math: true math: true
categories: [Mathematics] categories: [Mathematics]
tags: [math, study] tags: [math, study]
title: "수학 공부에 대한 고찰" title: "수학 공부에 대한 고찰"
date: "2022-02-03" date: "2022-02-03"
github_title: "2022-04-08-thoughts-on-studying-math" github_title: "2022-04-08-thoughts-on-studying-math"
--- ---
과외돌이 수업을 위해 새로운 교재를 골라야 했다. 교재를 고민하던 도중 내가 생각하는 수학 공부 방법을 설명하기에 매우 좋은 예시가 생겨서 이렇게 글로 남기게 되었다. 과외돌이 수업을 위해 새로운 교재를 골라야 했다. 교재를 고민하던 도중 내가 생각하는 수학 공부 방법을 설명하기에 매우 좋은 예시가 생겨서 이렇게 글로 남기게 되었다.
교재를 고민하면서 동생에게 블랙라벨 수학(상) 책이 있냐고 물어봤는데, 이미 버린 것 같다고 했다. 교재를 고민하면서 동생에게 블랙라벨 수학(상) 책이 있냐고 물어봤는데, 이미 버린 것 같다고 했다.
그러면서 나보고 현우진 뉴런 책에 있는 내용을 한 번 봤으면 좋겠다고 했다. 이유를 물어보니 ‘형도 그 책에 나와있는 내용 다 아나 싶어서?’라고 하길래, 한 번 훑어 봤다. 그러면서 나보고 현우진 뉴런 책에 있는 내용을 한 번 봤으면 좋겠다고 했다. 이유를 물어보니 ‘형도 그 책에 나와있는 내용 다 아나 싶어서?’라고 하길래, 한 번 훑어 봤다.
## 대칭성을 가진 함수의 적분 ## 대칭성을 가진 함수의 적분
딱히 특별한 내용은 없고 기본 개념 설명 되어있는 것 같아서 어디가 특별하냐고 했더니 예시로 한 부분을 보여줬는데, $x = a$ 에 대하여 대칭인 함수를 적분하는 방법, 점 대칭인 함수를 적분하는 방법에 대해 소개하고 있었다. 딱히 특별한 내용은 없고 기본 개념 설명 되어있는 것 같아서 어디가 특별하냐고 했더니 예시로 한 부분을 보여줬는데, $x = a$ 에 대하여 대칭인 함수를 적분하는 방법, 점 대칭인 함수를 적분하는 방법에 대해 소개하고 있었다.
1. $\mathbb{R}$에서 정의된 연속함수 $f(x)$의 그래프가 $x = m$ 에 대하여 대칭일 때, 1. $\mathbb{R}$에서 정의된 연속함수 $f(x)$의 그래프가 $x = m$ 에 대하여 대칭일 때,
$$\int_{m-a}^{m+a} f(x)\,dx = 2 \int_{m}^{m+a} f(x)\,dx.\quad (a \in \mathbb{R})$$ $$\int_{m-a}^{m+a} f(x)\,dx = 2 \int_{m}^{m+a} f(x)\,dx.\quad (a \in \mathbb{R})$$
2. $\mathbb{R}$에서 정의된 연속함수 $f(x)$의 그래프가 점 $(m, n)$에 대하여 대칭일 때, 2. $\mathbb{R}$에서 정의된 연속함수 $f(x)$의 그래프가 점 $(m, n)$에 대하여 대칭일 때,
$$\int_{m-a}^{m+a} f(x)\,dx = 2an. \quad (a \in \mathbb{R})$$ $$\int_{m-a}^{m+a} f(x)\,dx = 2an. \quad (a \in \mathbb{R})$$
보자마자 막 엄청 특별한 내용은 아니라는 것을 깨달았고, 이런 내용은 특별하게 누가 정리해주지 않아도 공부를 지엽적으로 하지 않고 충분히 생각을 많이 하면 알 수 있다고 얘기했다. 그렇다고 모르는게 이상하다는 의미는 절대 아니다. 보자마자 막 엄청 특별한 내용은 아니라는 것을 깨달았고, 이런 내용은 특별하게 누가 정리해주지 않아도 공부를 지엽적으로 하지 않고 충분히 생각을 많이 하면 알 수 있다고 얘기했다. 그렇다고 모르는게 이상하다는 의미는 절대 아니다.
## 스스로 깨닫는 방법 ## 스스로 깨닫는 방법
**동생**: 형은 그럼 이런 내용을 어떻게 알았어? **동생**: 형은 그럼 이런 내용을 어떻게 알았어?
이런 내용을 스스로 깨닫는 방법으로 2가지가 있다. 아래의 내용은 내가 공부법에 대한 얘기를 할 때 늘 강조하는 내용이기도 하다. 이런 내용을 스스로 깨닫는 방법으로 2가지가 있다. 아래의 내용은 내가 공부법에 대한 얘기를 할 때 늘 강조하는 내용이기도 하다.
적분 파트에서 \[우함수와 기함수의 적분\]은 기본 개념으로 대부분의 책이 가르친다고 가정한다. 적분 파트에서 \[우함수와 기함수의 적분\]은 기본 개념으로 대부분의 책이 가르친다고 가정한다.
1. $\mathbb{R}$에서 정의된 연속함수 $f(x)$가 우함수일 때, 1. $\mathbb{R}$에서 정의된 연속함수 $f(x)$가 우함수일 때,
$$\int_{-a}^{a} f(x)\,dx = 2 \int_{0}^{a} f(x)\,dx. \quad (a \in \mathbb{R})$$ $$\int_{-a}^{a} f(x)\,dx = 2 \int_{0}^{a} f(x)\,dx. \quad (a \in \mathbb{R})$$
2. $\mathbb{R}$에서 정의된 연속함수 $f(x)$의 그래프가 기함수일 때, 2. $\mathbb{R}$에서 정의된 연속함수 $f(x)$의 그래프가 기함수일 때,
$$\int_{-a}^{a} f(x)\,dx = 0. \quad (a \in \mathbb{R})$$ $$\int_{-a}^{a} f(x)\,dx = 0. \quad (a \in \mathbb{R})$$
### 개념 공부 하면서 스스로 일반화 해보기 ### 개념 공부 하면서 스스로 일반화 해보기
- 개념을 단편적인 지식으로 이해하고 끝낼 것이 아니라, 이 속에 들어있는 핵심 원리를 파악하고 일반화를 해야 한다. - 개념을 단편적인 지식으로 이해하고 끝낼 것이 아니라, 이 속에 들어있는 핵심 원리를 파악하고 일반화를 해야 한다.
- \[우함수 → $x = 0$ 에 대칭인 함수, 기함수 → 원점에 대칭인 함수\]라고 파악하게 되면 이 개념은 ‘우함수/기함수의 적분’이 아니라, ‘선대칭/점대칭 함수의 적분’이 된다. - \[우함수 → $x = 0$ 에 대칭인 함수, 기함수 → 원점에 대칭인 함수\]라고 파악하게 되면 이 개념은 ‘우함수/기함수의 적분’이 아니라, ‘선대칭/점대칭 함수의 적분’이 된다.
- 이렇게 하면, 배운 내용이 단순히 우함수/기함수일 때 쓰는 공식이 아니라, 피적분함수가 대칭성을 가질 때 사용할 수 있는 하나의 아이디어가 된다! - 이렇게 하면, 배운 내용이 단순히 우함수/기함수일 때 쓰는 공식이 아니라, 피적분함수가 대칭성을 가질 때 사용할 수 있는 하나의 아이디어가 된다!
- 공부하면서 개념에 대해 깊이 생각하고 고민하다 보면 자연스럽게 이런 내용을 깨닫게 되기도 한다. - 공부하면서 개념에 대해 깊이 생각하고 고민하다 보면 자연스럽게 이런 내용을 깨닫게 되기도 한다.
### 문제 풀면서 배운 것을 정리하기 ### 문제 풀면서 배운 것을 정리하기
- 문제를 풀고 나서 새롭게 알게 된 사실이나 깨달은 문제 풀이의 아이디어가 있다면, 다음에도 그 아이디어를 적용할 수 있도록 하기 위해, \[문제를 풀고 배운 것을 정리\]하고 넘어가야 한다. - 문제를 풀고 나서 새롭게 알게 된 사실이나 깨달은 문제 풀이의 아이디어가 있다면, 다음에도 그 아이디어를 적용할 수 있도록 하기 위해, \[문제를 풀고 배운 것을 정리\]하고 넘어가야 한다.
- **우리가 문제를 많이 푸는 이유를 따지고 보면 결국에는 비슷한 문제를 만났을 때 그것을 해결하기 위함이다.** - **우리가 문제를 많이 푸는 이유를 따지고 보면 결국에는 비슷한 문제를 만났을 때 그것을 해결하기 위함이다.**
- 문제집에서 A를 풀어서 제대로 익혔다면 다음에 A 문제를 만났을 때 수월하게 해결할 수 있을 것이다. 한편 A를 풀지 못해 답지를 보고 이해했으나 완전히 자신의 것으로 만들지 못했다면 다음에 A 을 만나도 문제를 풀지 못한다. - 문제집에서 A를 풀어서 제대로 익혔다면 다음에 A 문제를 만났을 때 수월하게 해결할 수 있을 것이다. 한편 A를 풀지 못해 답지를 보고 이해했으나 완전히 자신의 것으로 만들지 못했다면 다음에 A 을 만나도 문제를 풀지 못한다.
- 보통 문제를 풀다 보면 개념을 응용한 문제들이 나오기 때문에 단순히 우함수/기함수의 적분 뿐만 아니라 ‘대칭성을 가지는 함수의 적분’ 문제가 나오기 마련이다. - 보통 문제를 풀다 보면 개념을 응용한 문제들이 나오기 때문에 단순히 우함수/기함수의 적분 뿐만 아니라 ‘대칭성을 가지는 함수의 적분’ 문제가 나오기 마련이다.
- **이 문제를 출제 의도대로 해결한 경우**: 아마 높은 확률로 함수의 대칭성을 풀이에 활용했을 것이다. 문제를 풀고 나서 무엇을 배웠는지 정리할 때, \[대칭성을 가지는 함수를 적분하는 방법\]을 정리해서 앞으로 활용할 수 있도록 기억하면 된다. - **이 문제를 출제 의도대로 해결한 경우**: 아마 높은 확률로 함수의 대칭성을 풀이에 활용했을 것이다. 문제를 풀고 나서 무엇을 배웠는지 정리할 때, \[대칭성을 가지는 함수를 적분하는 방법\]을 정리해서 앞으로 활용할 수 있도록 기억하면 된다.
- **이 문제를 다른 방법으로 푼 경우**: 풀었더라도 해설에서 새로운 지식을 얻을 수도 있기 때문에 확인해 보는 것이 좋다. 뒤의 풀지 못한 경우에서 후술한다. - **이 문제를 다른 방법으로 푼 경우**: 풀었더라도 해설에서 새로운 지식을 얻을 수도 있기 때문에 확인해 보는 것이 좋다. 뒤의 풀지 못한 경우에서 후술한다.
- **이 문제를 풀지 못한 경우**: 해설을 보고 풀이를 공부하게 될 것이다. 그리고 해설에는 본인이 개념적으로 몰랐던 부분은 없을 확률이 높다. 아마 개념을 충분히 응용하지 못해 문제를 풀지 못한 것이다. (정말 특별한 아이디어가 필요한 경우가 아니라면) 못 풀어도 괜찮다. 해설을 보고 \[대칭성을 가지는 함수를 적분하는 방법\]을 정리하고 공부하여 부족했던 개념을 보충하면 된다. - **이 문제를 풀지 못한 경우**: 해설을 보고 풀이를 공부하게 될 것이다. 그리고 해설에는 본인이 개념적으로 몰랐던 부분은 없을 확률이 높다. 아마 개념을 충분히 응용하지 못해 문제를 풀지 못한 것이다. (정말 특별한 아이디어가 필요한 경우가 아니라면) 못 풀어도 괜찮다. 해설을 보고 \[대칭성을 가지는 함수를 적분하는 방법\]을 정리하고 공부하여 부족했던 개념을 보충하면 된다.
- 이 방법에서는 \[학생이 공부하다가 개념을 응용한 문제를 만났다\]는 가정을 하고 있다. 그래서 **좋은 문제집을 선택하는 것이 중요**하다. 단순히 개념을 적용하는 수준의 문제만 있지 않고, 학생이 개념을 응용할 수 있도록 하는 문제가 제공되는 책을 골라야 한다. (물론 1번 방법으로 하는 학생들은 그런 문제를 만나지 않아도 응용 문제를 잘만 해결하기 때문에, 적당한 개념서를 보는 것으로 충분한 경우도 있다. 다만 스스로 일반화를 할 생각을 할 정도의 내공과 수학적 직관을 갖고 있기는 어렵다는 것이 문제이다.) - 이 방법에서는 \[학생이 공부하다가 개념을 응용한 문제를 만났다\]는 가정을 하고 있다. 그래서 **좋은 문제집을 선택하는 것이 중요**하다. 단순히 개념을 적용하는 수준의 문제만 있지 않고, 학생이 개념을 응용할 수 있도록 하는 문제가 제공되는 책을 골라야 한다. (물론 1번 방법으로 하는 학생들은 그런 문제를 만나지 않아도 응용 문제를 잘만 해결하기 때문에, 적당한 개념서를 보는 것으로 충분한 경우도 있다. 다만 스스로 일반화를 할 생각을 할 정도의 내공과 수학적 직관을 갖고 있기는 어렵다는 것이 문제이다.)
첫 번째 방법으로 스스로 일반화 하면서 공부가 가능하다면 더할 나위 없이 좋겠지만, 현실적으로는 어렵다. 공부해야 할 양은 태산이기 때문에 깊게 생각할 시간은 사실상 거의 존재하지 않는다. 그리고 그 정도의 사고력을 태생적으로 갖고 있기도 어렵다. (그런 사고력을 가진 학생이라면 이미 공부를 잘 하지 않을까) 첫 번째 방법으로 스스로 일반화 하면서 공부가 가능하다면 더할 나위 없이 좋겠지만, 현실적으로는 어렵다. 공부해야 할 양은 태산이기 때문에 깊게 생각할 시간은 사실상 거의 존재하지 않는다. 그리고 그 정도의 사고력을 태생적으로 갖고 있기도 어렵다. (그런 사고력을 가진 학생이라면 이미 공부를 잘 하지 않을까)
그러므로 뉴런과 같이 특별한 개념이 따로 정리되어 있는 책의 도움을 받는 것도 충분히 좋은 방법이다! 물론 지나치게 특수한 개념에 지나치게 집중해버린 나머지 근본 원리를 잊어버리거나 큰 그림을 놓쳐서는 안될 것이다. 그러므로 뉴런과 같이 특별한 개념이 따로 정리되어 있는 책의 도움을 받는 것도 충분히 좋은 방법이다! 물론 지나치게 특수한 개념에 지나치게 집중해버린 나머지 근본 원리를 잊어버리거나 큰 그림을 놓쳐서는 안될 것이다.
## 이 내용은 어디에 쓰는가? ## 이 내용은 어디에 쓰는가?
**동생의 추가 질문**: 우함수/기함수/주기함수의 적분이 대체 공학적으로 무슨 쓸모가 있어? **동생의 추가 질문**: 우함수/기함수/주기함수의 적분이 대체 공학적으로 무슨 쓸모가 있어?
Fourier Series를 바로 꺼냈다. Fourier Series를 바로 꺼냈다.
- 모든 주기함수를 삼각함수의 무한 합으로 나타낼 수 있다! - 모든 주기함수를 삼각함수의 무한 합으로 나타낼 수 있다!
- 증명 과정에서 $\sin, \cos$ (주기함수이자 기함수, 우함수) 를 적분하는 아이디어를 사용 - 증명 과정에서 $\sin, \cos$ (주기함수이자 기함수, 우함수) 를 적분하는 아이디어를 사용
- 신호 처리 분야에서 굉장히 많이 쓰이는 내용이고, 이를 응용한 각종 Fourier Transform - DFT, FFT, QFT, STFT 등 - 은 공학 분야에서 이미 널리 쓰이는 중이다. - 신호 처리 분야에서 굉장히 많이 쓰이는 내용이고, 이를 응용한 각종 Fourier Transform - DFT, FFT, QFT, STFT 등 - 은 공학 분야에서 이미 널리 쓰이는 중이다.
나는 그냥 공부가 좋아서 하기 때문에 어떤 개념이 현실적으로 유용한지에 대해서는 많은 고민을 해보지 않는 편이다. 그래서 이 개념은 대칭성을 가지는 함수의 적분을 계산할 때 단순히 계산의 편의를 위해 존재한다고 생각하고 있었다. 동생의 질문 덕분에 이 개념이 현실에서 어떻게 사용되고 있는지 나도 깨닫게 되었다. 나는 그냥 공부가 좋아서 하기 때문에 어떤 개념이 현실적으로 유용한지에 대해서는 많은 고민을 해보지 않는 편이다. 그래서 이 개념은 대칭성을 가지는 함수의 적분을 계산할 때 단순히 계산의 편의를 위해 존재한다고 생각하고 있었다. 동생의 질문 덕분에 이 개념이 현실에서 어떻게 사용되고 있는지 나도 깨닫게 되었다.
알고있는 지식들이 서로 연결되어 깨달음을 얻었을 때 느껴지는 쾌감은 정말 최고다! 오늘도 지식이 늘었다! 👍 알고있는 지식들이 서로 연결되어 깨달음을 얻었을 때 느껴지는 쾌감은 정말 최고다! 오늘도 지식이 늘었다! 👍

View File

@@ -1,238 +0,0 @@
---
share: true
toc: true
math: true
categories: [Mathematics, Coq]
tags: [math, coq, proof-verification]
title: "Rules of Inference with Coq"
date: "2023-07-08"
github_title: "2023-07-08-rules-of-inference-with-coq"
---
This is a list of proofs with Coq, for each rule of inference stated in [List of Rules of Inference (Wikipedia)](https://en.wikipedia.org/wiki/List_of_rules_of_inference)
Some rules that need [the law of excluded middle](https://en.wikipedia.org/wiki/Law_of_excluded_middle) are at the end of the section.
## Rules for Negation
```coq
Lemma reductio_ad_absurdum : forall P Q : Prop,
(P -> Q) -> (P -> ~Q) -> ~P.
Proof.
intros. intros HP.
apply H0 in HP as HNQ.
apply H in HP as HQ.
contradiction.
Qed.
Lemma ex_contradictione_quodlibet : forall P Q : Prop,
P -> ~P -> Q.
Proof. intros. contradiction. Qed.
Lemma double_negation_introduction : forall P : Prop,
P -> ~~P.
Proof. auto. Qed.
```
## Rules for Conditionals
```coq
Lemma modus_ponens : forall P Q : Prop,
(P -> Q) -> P -> Q.
Proof. auto. Qed.
Lemma modus_tollens : forall P Q : Prop,
(P -> Q) -> ~Q -> ~P.
Proof. auto. Qed.
```
## Rules for Conjunctions
```coq
Lemma conjuction_introduction : forall P Q : Prop,
P -> Q -> P /\ Q.
Proof. auto. Qed.
Lemma conjunction_elimination_left : forall P Q : Prop,
P /\ Q -> P.
Proof. intros P Q [HP HQ]; auto. Qed.
Lemma conjunction_elimination_right : forall P Q : Prop,
P /\ Q -> Q.
Proof. intros P Q [HP HQ]; auto. Qed.
Lemma conjunction_commutative : forall P Q : Prop,
P /\ Q -> Q /\ P.
Proof.
intros. destruct H; split; auto.
Qed.
Lemma conjunction_associative : forall P Q R : Prop,
(P /\ Q) /\ R -> P /\ (Q /\ R).
Proof.
intros. destruct H as [H H1]; destruct H; split; auto.
Qed.
```
## Rules for Disjunctions
```coq
Lemma disjunction_introduction_left : forall P Q : Prop,
P -> P \/ Q.
Proof. auto. Qed.
Lemma disjunction_introduction_right : forall P Q : Prop,
Q -> P \/ Q.
Proof. auto. Qed.
Lemma disjunction_elimination : forall P Q R : Prop,
(P -> R) -> (Q -> R) -> (P \/ Q) -> R.
Proof.
intros. destruct H1; auto.
Qed.
Lemma disjunctive_syllogism_left : forall P Q : Prop,
(P \/ Q) -> ~P -> Q.
Proof.
intros. destruct H; auto. contradiction.
Qed.
Lemma disjunctive_syllogism_right : forall P Q : Prop,
(P \/ Q) -> ~Q -> P.
Proof.
intros. destruct H; auto. contradiction.
Qed.
Lemma constructive_dilemma : forall P Q R S : Prop,
(P -> R) -> (Q -> S) -> (P \/ Q) -> (R \/ S).
Proof.
intros. destruct H1; auto.
Qed.
Lemma disjunction_commutative : forall P Q : Prop,
P \/ Q -> Q \/ P.
Proof. intros. destruct H; auto. Qed.
Lemma disunction_associative : forall P Q R : Prop,
(P \/ Q) \/ R -> P \/ (Q \/ R).
Proof.
intros. destruct H as [H | H]; auto.
destruct H; auto.
Qed.
```
## Rules for Biconditionals
```coq
Lemma biconditional_introduction : forall P Q : Prop,
(P -> Q) -> (Q -> P) -> (P <-> Q).
Proof. split; auto. Qed.
Lemma biconditional_elimination_left_mp : forall P Q : Prop,
(P <-> Q) -> P -> Q.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_right_mp : forall P Q : Prop,
(P <-> Q) -> Q -> P.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_left_mt : forall P Q : Prop,
(P <-> Q) -> ~P -> ~Q.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_right_mt : forall P Q : Prop,
(P <-> Q) -> ~Q -> ~P.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_disjunction : forall P Q : Prop,
(P <-> Q) -> (P \/ Q) -> (P /\ Q).
Proof.
intros. destruct H, H0; auto.
Qed.
Lemma biconditional_elimination_disjunction_not : forall P Q : Prop,
(P <-> Q) -> (~P \/ ~Q) -> (~P /\ ~Q).
Proof.
intros. destruct H, H0; auto.
Qed.
```
## Other Rules
```coq
Lemma exportation : forall P Q R : Prop,
(P /\ Q) -> R <-> (P -> Q -> R).
Proof.
split; auto.
destruct H; auto.
Qed.
Lemma distributive_disjunction : forall P Q R : Prop,
P \/ (Q /\ R) <-> (P \/ Q) /\ (P \/ R).
Proof.
split; intros.
- destruct H as [H | [H1 H2]]; split; auto.
- destruct H as [H1 H2]; destruct H1, H2; auto.
Qed.
Lemma distributive_conjunction : forall P Q R : Prop,
P /\ (Q \/ R) <-> (P /\ Q) \/ (P /\ R).
Proof.
split; intros.
- destruct H as [H [H1 | H1]]; auto.
- destruct H as [ [H1 H2](H1%20H2.md); auto.
Qed.
Lemma material_implication_converse : forall P Q : Prop,
(~P \/ Q) -> (P -> Q).
Proof.
intros. destruct H; auto. contradiction.
Qed.
Lemma resolution : forall P Q R : Prop,
(P \/ Q) -> (~P \/ R) -> (Q \/ R).
Proof.
intros. destruct H, H0; auto. contradiction.
Qed.
```
## Rules that require Excluded Middle
We declare the law of excluded middle as an axiom.
```coq
Axiom excluded_middle : forall P : Prop, P \/ ~P.
Lemma double_negation_elimination : forall P : Prop,
~~P -> P.
Proof.
intros. destruct (excluded_middle P); auto. contradiction.
Qed.
Lemma material_implication : forall P Q : Prop,
(P -> Q) -> (~P \/ Q).
Proof.
intros. destruct (excluded_middle P); auto.
Qed.
Lemma reductio_ad_absurdum_neg : forall P Q : Prop,
(~P -> Q) -> (~P -> ~Q) -> P.
Proof.
intros. destruct (excluded_middle P); auto.
apply H in H1 as HQ.
apply H0 in H1 as HNQ.
contradiction.
Qed.
```
---
I was supposed to be reading the [source](https://github.com/snu-sf/promising-seq-coq) for the paper [Sequential Reasoning for Optimizing Compilers Under Weak Memory Concurrency](https://dl.acm.org/doi/abs/10.1145/3519939.3523718) but I got carried away...

View File

@@ -0,0 +1,238 @@
---
share: true
toc: true
math: true
categories: [Mathematics, Coq]
tags: [math, coq, proof-verification]
title: "Rules of Inference with Coq"
date: "2023-07-08"
github_title: "2023-07-08-rules-of-inference"
---
This is a list of proofs with Coq, for each rule of inference stated in [List of Rules of Inference (Wikipedia)](https://en.wikipedia.org/wiki/List_of_rules_of_inference)
Some rules that need [the law of excluded middle](https://en.wikipedia.org/wiki/Law_of_excluded_middle) are at the end of the section.
## Rules for Negation
```coq
Lemma reductio_ad_absurdum : forall P Q : Prop,
(P -> Q) -> (P -> ~Q) -> ~P.
Proof.
intros. intros HP.
apply H0 in HP as HNQ.
apply H in HP as HQ.
contradiction.
Qed.
Lemma ex_contradictione_quodlibet : forall P Q : Prop,
P -> ~P -> Q.
Proof. intros. contradiction. Qed.
Lemma double_negation_introduction : forall P : Prop,
P -> ~~P.
Proof. auto. Qed.
```
## Rules for Conditionals
```coq
Lemma modus_ponens : forall P Q : Prop,
(P -> Q) -> P -> Q.
Proof. auto. Qed.
Lemma modus_tollens : forall P Q : Prop,
(P -> Q) -> ~Q -> ~P.
Proof. auto. Qed.
```
## Rules for Conjunctions
```coq
Lemma conjuction_introduction : forall P Q : Prop,
P -> Q -> P /\ Q.
Proof. auto. Qed.
Lemma conjunction_elimination_left : forall P Q : Prop,
P /\ Q -> P.
Proof. intros P Q [HP HQ]; auto. Qed.
Lemma conjunction_elimination_right : forall P Q : Prop,
P /\ Q -> Q.
Proof. intros P Q [HP HQ]; auto. Qed.
Lemma conjunction_commutative : forall P Q : Prop,
P /\ Q -> Q /\ P.
Proof.
intros. destruct H; split; auto.
Qed.
Lemma conjunction_associative : forall P Q R : Prop,
(P /\ Q) /\ R -> P /\ (Q /\ R).
Proof.
intros. destruct H as [H H1]; destruct H; split; auto.
Qed.
```
## Rules for Disjunctions
```coq
Lemma disjunction_introduction_left : forall P Q : Prop,
P -> P \/ Q.
Proof. auto. Qed.
Lemma disjunction_introduction_right : forall P Q : Prop,
Q -> P \/ Q.
Proof. auto. Qed.
Lemma disjunction_elimination : forall P Q R : Prop,
(P -> R) -> (Q -> R) -> (P \/ Q) -> R.
Proof.
intros. destruct H1; auto.
Qed.
Lemma disjunctive_syllogism_left : forall P Q : Prop,
(P \/ Q) -> ~P -> Q.
Proof.
intros. destruct H; auto. contradiction.
Qed.
Lemma disjunctive_syllogism_right : forall P Q : Prop,
(P \/ Q) -> ~Q -> P.
Proof.
intros. destruct H; auto. contradiction.
Qed.
Lemma constructive_dilemma : forall P Q R S : Prop,
(P -> R) -> (Q -> S) -> (P \/ Q) -> (R \/ S).
Proof.
intros. destruct H1; auto.
Qed.
Lemma disjunction_commutative : forall P Q : Prop,
P \/ Q -> Q \/ P.
Proof. intros. destruct H; auto. Qed.
Lemma disunction_associative : forall P Q R : Prop,
(P \/ Q) \/ R -> P \/ (Q \/ R).
Proof.
intros. destruct H as [H | H]; auto.
destruct H; auto.
Qed.
```
## Rules for Biconditionals
```coq
Lemma biconditional_introduction : forall P Q : Prop,
(P -> Q) -> (Q -> P) -> (P <-> Q).
Proof. split; auto. Qed.
Lemma biconditional_elimination_left_mp : forall P Q : Prop,
(P <-> Q) -> P -> Q.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_right_mp : forall P Q : Prop,
(P <-> Q) -> Q -> P.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_left_mt : forall P Q : Prop,
(P <-> Q) -> ~P -> ~Q.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_right_mt : forall P Q : Prop,
(P <-> Q) -> ~Q -> ~P.
Proof.
intros. destruct H; auto.
Qed.
Lemma biconditional_elimination_disjunction : forall P Q : Prop,
(P <-> Q) -> (P \/ Q) -> (P /\ Q).
Proof.
intros. destruct H, H0; auto.
Qed.
Lemma biconditional_elimination_disjunction_not : forall P Q : Prop,
(P <-> Q) -> (~P \/ ~Q) -> (~P /\ ~Q).
Proof.
intros. destruct H, H0; auto.
Qed.
```
## Other Rules
```coq
Lemma exportation : forall P Q R : Prop,
(P /\ Q) -> R <-> (P -> Q -> R).
Proof.
split; auto.
destruct H; auto.
Qed.
Lemma distributive_disjunction : forall P Q R : Prop,
P \/ (Q /\ R) <-> (P \/ Q) /\ (P \/ R).
Proof.
split; intros.
- destruct H as [H | [H1 H2]]; split; auto.
- destruct H as [H1 H2]; destruct H1, H2; auto.
Qed.
Lemma distributive_conjunction : forall P Q R : Prop,
P /\ (Q \/ R) <-> (P /\ Q) \/ (P /\ R).
Proof.
split; intros.
- destruct H as [H [H1 | H1]]; auto.
- destruct H as [ [H1 H2](H1%20H2.md); auto.
Qed.
Lemma material_implication_converse : forall P Q : Prop,
(~P \/ Q) -> (P -> Q).
Proof.
intros. destruct H; auto. contradiction.
Qed.
Lemma resolution : forall P Q R : Prop,
(P \/ Q) -> (~P \/ R) -> (Q \/ R).
Proof.
intros. destruct H, H0; auto. contradiction.
Qed.
```
## Rules that require Excluded Middle
We declare the law of excluded middle as an axiom.
```coq
Axiom excluded_middle : forall P : Prop, P \/ ~P.
Lemma double_negation_elimination : forall P : Prop,
~~P -> P.
Proof.
intros. destruct (excluded_middle P); auto. contradiction.
Qed.
Lemma material_implication : forall P Q : Prop,
(P -> Q) -> (~P \/ Q).
Proof.
intros. destruct (excluded_middle P); auto.
Qed.
Lemma reductio_ad_absurdum_neg : forall P Q : Prop,
(~P -> Q) -> (~P -> ~Q) -> P.
Proof.
intros. destruct (excluded_middle P); auto.
apply H in H1 as HQ.
apply H0 in H1 as HNQ.
contradiction.
Qed.
```
---
I was supposed to be reading the [source](https://github.com/snu-sf/promising-seq-coq) for the paper [Sequential Reasoning for Optimizing Compilers Under Weak Memory Concurrency](https://dl.acm.org/doi/abs/10.1145/3519939.3523718) but I got carried away...