[CKA] Section 2: Core Concepts
Coding

[CKA] Section 2: Core Concepts

본 글은 Udemy 강의 중 Certified Kubernetes Administrator (CKA) with Practice Tests
내용을 임의로 정리한 내용입니다.

12. Docker-vs-ContainerD

  • 태초엔 Docker가 있었다. Docker가 융성하면서 이를 효율적으로 관리하기 위해 k8s가 태어났다.
  • k8s 커뮤니티가 점점 커지기 시작하자, Rocket과 같은 다른 컨테이너 런타임도 k8s에 들어오고 싶어했다.
  • k8s는 그 생명력을 위해서라도 확장성을 가져야 했다. 그래서 다른 컨테이너 런타임도 원한다면 생태계에 들어올 수 있도록 인터페이스를 제공했다. 그게 **Container Runtime Interface** (CRI)다.
  • CRI를 구현한 컨테이너 런타임은 Open Container Initiative (OCI) 프로토콜만 지키면 k8s에 편입될 수 있었다. OCI는 크게 imagespec과 runtimespec으로 나뉜다.
  • 근데 문제는 Docker부터가 CRI를 따르지 않는다는 점이다. 시간 순서상 당연하다. Docker가 먼저 나왔고 표준이라고 할 수 있는 CRI는 그 이후에나 나왔기 때문이다.
  • 그렇다고 k8s가 Docker를 무시할 수도 없는 노릇이었다. 가장 큰 고객이기 때문. k8s가 Docker를 지원하기 위해 내놓은 것이 walkaround 방식인 dockershim이다.
  • Docker는 실제로 여러 컴포넌트의 집합이다. API, Volume, Auth 등… 그 컴포넌트 중 하나가 containerD다. containerD는 컨테이너를 실행하고 이미지를 관리한다.
  • 2016년에 Docker는 containerD를 독립 프로젝트로 분리했고 containerD는 주요 기술 기업들이 뛰어들어 발전시켰다. 그결과 containerD는 컨테이너 기술에서 사실상 표준이 되었다. containerD는 OCI 표준을 지킨다.
  • Docker는 내부적으로 containerD를 사용하지만 containerD는 스탠드얼론이다. 그러므로 containerD만으로 컨테이너를 띄울 수도 있다! 어떻게? containerD의 CLI가 ctr이다. 매우 제한적인 CLI 툴이긴 하다.
  • ctr을 Docker CLI와 비슷한 느낌으로 쓰기 위해서 nerdctl을 쓸 수 있다.
  • crictl은 위에서 말한 CRI에 접근하는 CLI툴이다. 이걸 통해서 어느 컨테이너 기술이든 CRI에 맞게 inspect와 debug를 할 수 있다. 쿠버네티스가 컨테이너 런타임을 보는 인터페이스라고 생각하면 된다. (k8s 입장에선 컨테이너 런타임 뭐든지 상관없이 일관된 방식으로 디버깅할 수 있어야 한다)

13. ETCD for Beginners

  • 분산되어 있는 - 안전한 - 키밸류 스토어
  • ./etcd 로 etcd 서버를 시작할 수 있다. etcd 서버는 2379 포트를 열어둔다.
  • 아래처럼 etcdctl cli client로 새로운 값을 추가할 수 있다. ./etcdctl set key1 value1 ./etcdctl get key1
  • 2015년 2월에 v2.0이 출시되었고 다음 버전인 v3.1은 2017년 1월에 출시되었다. 이 사이에 큰 변화가 있었다. etcd의 API 버전이 2에서 3으로 바뀌었다. (위에 커맨드는 2버전이다.)
  • 3버전은 데이터 추가가 바뀌었을 정도로 많이 바뀌었다. ./etcdctl put key1 value1 ./etcdctl get key1
  • API 버전을 바꾸고 싶으면 환경변수를 바꾸면 된다. export ETCDCTL_API=3

14. ETCD in k8s

  • k8s에 필요한 모든 정보가 ETCD에 저장되고 수정된다. Nodes, PODs, Configs, Secrets, Accounts 등
  • k8s 클러스터 배포에는 크게 2가지 방법이 있다.
    • Manual
      • 따로 ETCD를 설치하고 실행해야 한다.
      • etcd.service에 설정하는 설치 옵션 중에서 --advertise-client-urls가 중요한데, 이게 ETCD 서버의 엔드포인트이기 때문이다.
    • kubeadm이 알아서 ETCD를 POD 방식으로 띄워준다.
  • HA 환경에서는 ETCD도 여러개 인스턴스가 떠있을 것이다.

16. Kube-API Server

  • kubectl 커맨드를 입력하면 이 커맨드는 가장 먼저 Kube-API Server에 도달한다. Kube-API Server는 아래 동작을 수행한다.
    • 요청의 권한과 유효성을 검사한다.
    • 필요한 작업을 수행(Node 생성 등)
    • 필요하다면 ETCD 서버에 데이터를 업데이트한다.
  • Kube-API는 k8s의 모든 컴포넌트 간 통신을 책임진다. 이 때, TLS를 통해 보안을 적용한다. (모든 컴포넌트 간 통신에 TLS가 필요하다)
  • Kube-API 서버의 설정은 아래 경로에서 확인할 수 있다.
    cat /etc/systemd/system/kube-apiserver.service

17. Kube Controller Manager

  • Kube Controller Manager는 각기 다른 역할을 가진 오피스의 연합이라고 이해하면 된다. (또는 클러스터의 뇌)
  • Node-Controller는 Node의 상태를 주시하며 그에 따른 대응을 수행한다. 당연히 이 대응도 Kube-API Server를 통한다.
  • 이렇게 많은 것들이 Kube Controller Manager라는 하나의 프로세스로 떠서 수행된다.

18. Kube Scheduler

  • Scheduler는 어떤 POD가 어느 Node에 가야할 지 결정하는 것만 수행한다. 실제로 POD를 Node에 올리지는 않는다.
  • 실제로 올리는 건 Kubelet의 역할이다.
  • 왜 스케쥴러가 필요할까?
    • 수많은 배(=Node)가 있다고 했을 때, 컨테이너(=Pod)를 어떤 배에 실을 지 결정하는 건 생각보다 쉬운 일이 아니다. 컨테이너를 실을 수 있을 정도로 배에 Capacity가 있는 지도 알아야 하고 성능도 알아야 한다.
    • 또한 각 컨테이너가 어디에 도착(Endpoint)해야 하는지도 알아야 한다.

19. Kubelet

  • Kubelet은 배(=워커 노드)의 선장이다. 배 안에서 일어나는 모든 일을 관장한다. 마스터 쉽(=컨트롤러 노드)와도 교류하는 서류 작업도 해야 한다.
  • Scheduler가 배에 컨테이너를 실어야 겠다고 결정 → Scheduler가 API Server를 통해 Kubelet에 요청 → Kubelet은 컨테이너 런타임을 통해 Pod를 생성
  • Kubeadm은 다른 컴포넌트와 달리 자동으로 Kubelet을 자동으로 설치하지 않는다.

20. Kube-proxy

  • 노드에 배포된 pod는 다른 pod와 통신할 수 있다. 이걸 가능하게 해주는 게 Kube Proxy다.

21. Recap - Pods

  • 아래 환경이 다 갖춰졌다고 가정한다.
    • 애플리케이션은 개발이 완료되어 이미지로 빌드되었다.
    • k8s 클러스터가 배포되어 실행 중이다.
    • 싱글 노드 셋업이든 멀티 노드 셋업이든 상관없다.
  • k8s는 절대 컨테이너를 워커 노드에 직접 배포하지 않는다. POD로 캡슐화해서 배포한다. Pod는 애플리케이션 인스턴스 한 개다.
  • Pod는 k8s로 만들 수 있는 가장 작은 단위다. 즉, 트래픽이 많아져서 더 많은 앱이 필요하다면 POD에 두 개 앱을 띄울 수는 없고 워커노드에 Pod를 더 배포해야 한다.
  • 만약 워커 노드에 Capacity가 부족하다면 워커 노드를 더 배포해야 한다.
  • 그렇다면 POD는 무조건 한 개 컨테이너만 배포할 수 있을까? 그렇지 않다. 보통 같은 컨테이너 여러개를 한 POD에 배포하진 않을 뿐이다. 보통 App과 Helper 컨테이너를 함께 배포한다.
  • 이렇게 보면 왜 굳이 POD가 필요할까 싶을 것이다. Node > POD > Container > App의 4중 구조가 너무 과해보이는 것이다. 하지만 이 구조는 Long Run을 염두에 뒀을 때 매우 유리하다. POD가 없이(그나마 뺄 수 있는게 POD니까) 여러개 컨테이너를 띄웠다면 이들 사이에 볼륨이나 네트워크를 전부 수동으로! 설정해줘야 한다. 만약 Helper Container가 있다면 그것도 수동으로 만들어주고 네트워크 설정도 해줘야 할 것이다. POD는 이것들을 전부 알아서 해준다.
  • 실제로 POD는 어떻게 배포할까? 아래 명령어를 사용한다. kubectl run nginx --image nginx
  • nginx 이미지를 가지고 POD를 배포한다. 아래 명령어로 확인할 수 있다. kubectl get pods

22. Pods with YAML

  • POD를 배포하는데 필요한 모든 정보를 YAML에 담아서 저장하고 실행할 수 있다.
apiVersion: v1
kind: Pod
metadata:
	name: myapp-pod
	label:
		app: myapp
		type: front-end
spec:
	containers:
		- name: nginx-container
			image: nginx
  • kubectl create -f pod-definition.yml
  • kubectl get pods : 전체 POD 목록 조회
  • kubectl describe pod myapp-pod : POD 상세 조회

29. Recap - ReplicaSets

Replication Controller

POD를 특정 개수로 유지해준다.

  • HA를 위해 여러 POD를 띄우도록 도와준다.
  • 그렇다면 Single POD를 띄울 생각이면 Replication Controller 필요없을까? 아니다. Replication Controller를 통해 POD가 죽으면 자동으로 살리도록 설정할 수 있다.

로드밸런싱 & 스케일링

  • 트래픽이 많아짐에 따라 POD를 늘리고 트래픽을 분산시킬 수 있다. 만약 워커 노드의 리소스가 부족하다면 다른 워커 노드에도 POD를 배포할 수 있다.

vs Replica Set

  • Replication Controller가 더 오래된 개념이고 Replica Set으로 대체되었다.
  • Replica Set이 더 선호되고 위 기능도 다 지원한다. 즉, Replica Set이 Replica Controller의 상위호환이다.

yaml로 Replication Controller 생성하기

apiVersion: 1 # RC는 1만 지원한다.
kind: ReplicationController
metadata:
	name: myapp-rc
	labels:
		app: myapp
		type: front-end
spec:
	template:
		metadata:
			name: myapp-pod
			labels:
				app: myapp
				type: frontend
		spec:
			containers:
			- name: nginx-container
				image: nginx
	replicas: 3
		

Replica Sets

yaml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
	name: myapp-replicaset
	labels:
		app: myapp
		type: front-end
spec:
	template:
		metadata:
			name: myapp-pod
			labels:
				app: myapp
				type: frontend
		spec:
			containers:
			- name: nginx-container
				image: nginx
	replicas: 3
	selector: # major difference
		matchLabels:
			type: front-end
	

Labels and Selectors

  • Replica Set은 단순히 POD 배포가 아니라 관리까지 역할한다.
  • Replica Set은 어떻게 자기가 봐야할 POD를 선별할까? 백만개 POD 중에서 자기가 관리해야 하는 POD가 뭔지 어떻게 알 수 있는가?
  • select가 바로 그 필터다.

Scale

  • replicas를 3에서 6으로 늘리고 싶다면 어떻게 할까? 가장 쉬운 방법은 yaml에서 숫자를 고친 후 아래 커맨드를 입력하는 것이다. kubectl replace -f replicaset-definition.yml
  • kubectl scale --replicas=6 -f replicaset-definition.yml 도 된다. kubectl scale —replicas=6 replicaset myapp-replicaset

32. Deployments

  • ReplicaSet 같은 개념은 모두 잊고 일단 아래 상황에 집중해보자.
    • 백엔드 서버를 여러개 띄워야 하는 상황이다.
    • 이때, 새로운 버전이 Docker Registry에 등록되면 띄워진 백엔드 서버도 부드럽게 전환하고 싶다.
    • 이 전환이 제대로 안됐다면 버전 롤백도 할 수 있어야 한다.
  • 이걸 해주는게 Deployment다. 적용하는 방법은 쉽다. 그냥 위에 yaml에서 kind를 Deployment로 바꾸면 된다.

36. Services

  • 클러스터 외부와 통신할 수 있게 해준다.
  • 예를 들어, FE POD 그룹을 외부 클라이언트와 연결해주고 BE POD 그룹을 DB와 연결해준다. 또한 다른 POD 그룹 간에도 연결해준다.
  • 예를 들어, 아래와 같은 상황을 가정해보자.
    • Client : 192.168.1.10
    • Worker Node : 192.168.1.2
    • POD Network : 10.244.0.0
    • HTTPD POD : 10.244.0.2
  • Client가 HTTPD POD에 http 요청을 보내려면 어떻게 해야할까?
  • 가장 직접적인 방법은 SSH로 Worker Node에 접속한 다음, curl <http://10.244.0.2> 를 하는 것이다. 이 방법은 외부에 서비스를 제공할 수 없기 때문에 의미가 없다.
  • 이 때, 필요한게 Service다.
  • Service는 앞서 배운 ReplicaSet 또는 Deployment와 같이 k8s 오브젝트다. Service 역할 중하나가 Worker Node의 Port를 리슨해서 트래픽을 POD에 포워딩해주는 것이다. 즉, Service를 통해 우리는 SSH 연결없이 바로 아래 요청이 가능하다. curl http://192.168.1.2:30008
  • Service Type은 아래와 같다.
    • NodePort : 트래픽을 포워딩해서 POD에 전해준다.
    • ClusterIP : Virtual IP를 생성해 Cluster 내부 통신을 지원한다.
    • LoadBalancer : 지원하는 클라우드 프로바이더라면 로드밸런싱
  • yaml
    • 만약 selector에 해당하는 pod가 많으면 기본적으로 랜덤으로 할당한다.
  • apiVersion: v1 kind: Service metadata: name: myapp-service spec: type: NodePort ports: - targetPort: 80 # 대상 POD가 공개한 port port: 80 # 서비스의 port nodePort: 30008 # 워커 노드에 연결할 port selector: app: myapp type: front-end
  • Service는 노드 안에 있는건데, 대상이 여러 노드안에 있으면 어떻게 될까? k8s가 알아서 멀티 노드로 서비스를 만들어준다.

37. Services - Cluster IP

  • FE POD가 여러대 있고 BE POD도 여러대, DB POD도 여러대가 있을 때, 이들 사이의 연결을 어떻게 설정해야 할까?
  • 각 POD는 유동 IP를 가지고 있을것이다. 이 IP는 유동이니까 사용할 순 없다.
  • k8s Service를 사용해서 POD들을 그루핑하고 이 그룹에 접근하기 위한 싱글 인터페이스를 제공할 수 있다.
  • yaml
  • apiVersion: v1 kind: Service metadata: name: back-end spec: type: ClusterIP ports: - targetPort: 80 port: 80 selector: app: myapp type: back-end
  • 사용
  • kubectl create -f service-definition.yml kubectl get services

38. Services - Loadbalancers

  • yaml
  • apiVersion: v1 kind: Service metadata: name: back-end spec: type: LoadBalancer ports: - targetPort: 80 port: 80 nodePort: 30008

41. Namespaces

  • Default 네임스페이스는 k8s가 생성한 네임스페이스다.
  • k8s가 시스템 구성을 위해 생성한 자원은 kube-system 네임스페이스를 가진다.
  • k8s가 생성한 자원 중 모든 유저가 사용할 수 있는 자원은 kube-public네임스페이스를 달고 있다.

Isolation

  • 네임스페이스의 기본 기능이 Isolation이다.
  • 개발계와 운영계가 같은 클러스터를 쓰되, 격리를 하고 싶다면 각자 다른 네임스페이스를 사용하면 된다.
  • 네임스페이스는 멀티 노드이지만 리소스 제한을 걸 수 있다.

DNS

  • 같은 네임스페이스를 가진 POD는 서로를 Name으로 바로 찾아낼 수 있다. mysql.connect(”db-service”)
  • 다른 네임스페이스의 POD를 찾아내려면 그 네임스페이스를 전부 써줘야 한다. mysql.connect(”db-service.dev.svc.cluster.local”)

Commands

kubectl get pods # Default NS의 모든 POD
kubectl get pods --namespace=kube-system # kube-system NS의 모든 POD
kubectl create -f definition.yaml # Default NS에 POD 배포
kubectl create -f definition.yaml --namespace=dev # dev NS에 POD 배포
# NS를 지정하려면 yaml에 meta > namespace에 작성할 수도 있다.

Create Namespace

by YAML file

apiVersion: v1
kind: Namespace
metadata:
	name: dev

by Commands

kubectl create namespace dev

Switch current namespace

kubectl config set-context $(kubectl config current-context) \\
--namespace=dev

kubectl get pods # dev NS의 POD 검색

Set NS ResourceQuota

apiVersion: v1
kind: ResourceQuota
metadata:
	name: compute-quota
	namespace: dev
spec:
	hard:
		pods: "10"
		requests.cpu: "4"
		requests.memory: 5Gi
		limits.cpu: "10"
		limits.memory: 10Gi

Imperative vs Declarative

  • Imperative Approach는 택시 기사님께 갈림길에서 어디로 꺾으면 되는지 하나하나 설명하는 것과 같다. = 명령어를 하나하나 다 쓰는 것
  • Declarative Approach는 택시 기사님께 도착지 주소를 알려주는 것과 같다. = yaml을 하나 작성해서 실행하는 것 = 결과물을 예측하기 쉽고 git 등으로 리뷰를 받을 수도 있다.

'Coding' 카테고리의 다른 글

Animation Blueprints  (0) 2022.02.02
BlueprintCallable, BlueprintImplementableEvent, BlueprintNativeEvent  (0) 2021.11.16