쿠버네티스를 다루기 위해 가장 많이 사용하는 명령어는 kubectl입니다.
하지만 kubectl을 사용할 때 키보드를 7번이나 입력해야 하는 불편함이 있습니다.
또한 리눅스에서 자주 용하는 배시 셸처럼 Tab을 통해 입력 가능한 명령이 확인되지 않아
모든 명령을 암기하고 있어야 합니다.
이런 불편함을 해소하고, 자주 쓰는 명령을 편하게 구현하는 방법을 알아봅시다!
쿠버네티스는 배시 셸의 명령을 자동으로 완성하기 위해 bash-completion이라는 패키지를 제공합니다.
bash-completion은 배시의 내장(Built-in) 명령 중 하나인 complete
을 이용해 특정 명령에 대한 자동 완성 목록을 표시합니다.
kubectl
은 kubectl completion bash
명령을 제공하고, 이를 실행하면 complete에 맞게 자동 완성에 필요한 목록을 생성합니다.
따라서 kubectl 명령에 대한 자동 완성 목록을 구현하면 kubectl 이후에 입력하는 명령들은 tab을 이용해 사용할 수 있습니다.
kubectl
을 입력하고 Tab을 눌러보세요!
kubectl을 자동 완성형으로 쓰기 위해서는 kubectl completion bash로 나온 결과를
설정(/etc/bash_completion.d/kubectl)에 저장하고 이것을 접속 시에
배시 셸 설정(~/.bashrc)에서 불러오도록 하면 됩니다.
이와 같은 목적으로 작성된 코드는 다음과 같습니다.
#!/usr/bin/env bash
#Usage:
#1. bash <(curl -s https://raw.githubusercontent.com/sysnet4admin/IaC/master/manifests/bash-completion.sh)
# install bash-completion for kubectl
yum install bash-completion -y
# kubectl completion on bash-completion dir
kubectl completion bash >/etc/bash_completion.d/kubectl
# alias kubectl to k
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -F __start_kubectl k' >> ~/.bashrc
#Reload rc
su -
kubectl 명령 자동 완성은 이미 이전 포스팅에서 멀티스테이지를 위한 새로운 쿠버네티스를 배포할 때 설정이 포함돼 들어갔습니다.
따라서 포스팅은 순서대로 따라오며 실습하셨다면 추가로 실행할 필요는 없으나,
바로 이번 포스팅을 보신다면 2~3번째 줄에 나온 사용법(usage) 또는 다음과 같은 명령을 실행하면 순쉽게 설정됩니다.
~/_Book_k8sInfra/app/A.kubectl-more/bash-completion.sh
혹은
curl -s https://raw.githubusercontent.com/sysnet4admin/IaC/master/manifests/bash-completion.sh
설정하고 나면 다음과 같이 kubectl <특정 명령어>
이후로 tab을 누르면 명령어 이후에 사용할 수 있는 명령어가 어떤 것이 있는지 확인할 때 유용합니다!
kubectl expose <tab>
특히 자동 완성은 describe
또는 exec
와 같이 파드 이름이 필요한 경우에 편리하게 사용할 수 있습니다.
kubectl describe pods <tab>
이제 kubectl을 사용할 때 쿠버네티스에서 제공하는 모든 오브젝트를 외울 필요가 없어졌습니다.
하지만 반복적으로 파드의 상태를 확인하거나 파드에 접속하다 보면 kubectl을 입력한 다음에
Tab을 여러 번 눌러야 합니다. 여전히 남아 있는 불편함을 해결할 방법은 없을까요?
첫 번쨰로 해결할 것은 kubectl 명령어를 k로 별칭(alias)을 만들어 사용하는 것입니다.
# alias kubectl to k
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -F __start_kubectl k' >> ~/.bashrc
이렇게 설정되고 나면 다음과 같이 k
또는 k <특정 명렁어>
이후로 Tab을 누르면,
명령어 이후에 사용할 수 있는 명령어가 어떤 것이 있는지 kubectl과 똑같이 나옵니다.
단 k
로 시작하는 명령어와 k
(별칭)로 시작하는 명령은 다릅니다!
두 번째로 해결할 것은 반복적으로 입력해야 하는 긴 명령 구문입니다.
배시 스크립트를 작성해 명령 구문을 미치 별칭으로 만들고,
필요에 따라 입력 값을 받아오면 원하는 작업을 할 수 있습니다.
배시 스크립트로 어떤 작업을 할 수 있는지 가볍게 테스트해 봅시다!
kubectl의 alias 집합이 있는 k8s_rc.sh를 실행합니다.
~/_Book_k8sInfra/app/A.kubectl-more/k8s_rc.sh
kgp를 실행해 파드의 상태를 확인합니다.(배포된 파드가 없다면 먼저 파드를 배포하기 바랍니다.)
명령 결과로 확인된 파드에 접속하기 위해서 기존에는 kubectl exec
를 입력하고 접속할 파드를 추가 입력해야 했지만, 이러한 불편함을 해결한 keq
를 실행해서 파드에 접속해 보겠습니다.
cat /run/nginx.pid
를 실행해 동작하는지 확인합니다. 동작하면 pid가 1로 표시됩니다.(nginx pods의 경우)
cat /run/systemd/notify NOTICE.md
kubectl을 alias하면 많은 부분을 편하게 사용할 수 있지만, 모든 부분을 alias할 순 없습니다.
떄로는 명령어를 직접 쳐서 확인할 수밖에 없습니다.
그래서 kubectl은 자주 사용하는 구문의 약어(short name)을 지원하고 있습니다.
앞에서 이미 일부 배포 이름에 약어를 사용했습니다.
deploy, hpa, sts처럼요.
몇 가지 약어를 간단히 테스트해 봅시다!
k get no(nodes)
로 노드의 상태를 확인합니다.k get po(pods)
로 파드의 상태를 확인합니다.k delete deploy nginx
로 deployment를 삭제합니다.주로 사용하는 약어는 다음과 같습니다.
구분 | 이름 | 약어 | 오브젝트 이름(Kind) |
---|---|---|---|
자주 사용하는 명령어 | nodes | no | Node |
namespaces | ns | Namespace | |
deployments | deploy | Deployment | |
pods | po | Pod | |
services | svc | Service | |
앞선 포스팅에서 다룬 명령어 | replicasets | rs | ReplicaSet |
ingresses | ing | Ingress | |
configmaps | cm | ConfigMap | |
horizontalpodautoscalers | hpa | HorizontalPodAutoscaler | |
daemonsets | ds | DaemonSet | |
persistentvolumeclaims | pvc | PersistentVolumeClaim | |
persistentvolumes | pv | PersistentVolume | |
statefulsets | sts | StatefulSet | |
참고 명령어 | replicationcontrollers | rc | ReplicationController |
resourcequotas | quota | ResourceQuota | |
serviceaccounts | sa | ServiceAccount | |
cronjobs | cj | CronJob | |
events | ev | Event | |
storageclasses | sc | StorageClass | |
endpoints | ep | Endpoints | |
limitranges | limits | LimitRange |
약어는 쿠버네티스 버전이 업데이트됨에 따라 변경될 수 있습니다.
현재 쿠버네티스 버전의 약어는 kubectl api-resources
명령어로 확인할 수 있습니다.
이전 포스팅에서 프로메테우스로 메트릭 데이터를 수집하고
그파나로 시각화해 쿠버네티스 클러스터 상태를 확인해 봤습니다.
하지만 작은 프로젝트나 소규모 환경에서는 이렇게까지 모니터링하고 관리할 필요는 없습니다.
그래서 이번에는 쿠버네티스에서 추가적으로 제공하는 기능인
쿠버 대시보드(kube-dashboard) 로 간단한 웹 UI를 구성해 쿠버네티스 클러스터의 상태를 확인하고,
나아가 배포한 쿠버 대시보드를 통해 쿠버네티스 클러스터에 오브젝트를 배포하는 방법도 알아보겠습니다.
쿠버 대시보드(이후 대시보드로 칭합니다)를 기본 설정으로 배포할 경우
웹 브라우저에서 HTTPS(예외: 로컬호스트로 접근하는 경우)로 대시보드에 접속해야 합니다.
여기선 편의를 위해 HTTP로 접근할 수 있게 설정하고 대시보드의 로그인 인증을 생략하는 옵션을 추가하겠습니다.
또한 대시보드에서 쿠버네티스 클러스터의 오브젝트를 관리하기 위해
클러스터의 모든 오브젝트에 접근하고 변경할 수 있는 역할을 추가하겠습니다.
실습에서 다루는 대시보드는 접속할 떄 로그인할 필요가 없어서 누가 접속했는지 알 수 없습니다.
이러한 상태의 대시보드를 실제 환경에서 공개적으로 배포하면
인증되지 않은 사용자가 대시보드를 통해 쿠버네티스 클러스터의
오브젝트를 조회, 수정, 삭제하거나 생성할 수 있습니다.
그러므로 상용 환경에서 대시보드를 공개적으로 배포하는 것은 권장하지 않습니다.
먼저 사전에 구성한 대시보드의 매니페스트 파일을 쿠버네티스 클러스터에 배포하겠습니다.
kubectl apply -f ~/_Book_k8sInfra/app/B.kube-dashboard/dashboard.yaml
```
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: v1
kind: Namespace
metadata:
name: kubernetes-dashboard
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
ports:
- port: 80
targetPort: 9090
# 외부로 접근하기 위해 31000번 포트를 노드 포트로 노출합니다.
nodePort: 31000
selector:
k8s-app: kubernetes-dashboard
type: NodePort
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-certs
namespace: kubernetes-dashboard
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-csrf
namespace: kubernetes-dashboard
type: Opaque
data:
csrf: ""
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-key-holder
namespace: kubernetes-dashboard
type: Opaque
---
kind: ConfigMap
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-settings
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
containers:
- name: kubernetes-dashboard
image: kubernetesui/dashboard:v2.0.3
imagePullPolicy: Always
ports:
- containerPort: 9090
protocol: TCP
args:
# 대시보드 접근 시 인증을 생략할 수 있게 합니다.
- --enable-skip-login
- --disable-settings-authorizer=true
# 모든 IP에서 HTTP로 접속할 수 있게 합니다.
- --enable-insecure-login
- --insecure-bind-address=0.0.0.0
- --namespace=kubernetes-dashboard
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.
# - --apiserver-host=http://my-address:port
volumeMounts:
- name: kubernetes-dashboard-certs
mountPath: /certs
# Create on-disk volume to store exec logs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
scheme: HTTP
path: /
port: 9090
initialDelaySeconds: 30
timeoutSeconds: 30
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
volumes:
- name: kubernetes-dashboard-certs
secret:
secretName: kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
# 대시보드를 마스터 노드에 배포하도록 설정합니다.
nodeSelector:
"kubernetes.io/hostname": m-k8s
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
ports:
- port: 8000
targetPort: 8000
selector:
k8s-app: dashboard-metrics-scraper
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: dashboard-metrics-scraper
template:
metadata:
labels:
k8s-app: dashboard-metrics-scraper
annotations:
seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
spec:
containers:
- name: dashboard-metrics-scraper
image: kubernetesui/metrics-scraper:v1.0.4
ports:
- containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
scheme: HTTP
path: /
port: 8000
initialDelaySeconds: 30
timeoutSeconds: 30
volumeMounts:
- mountPath: /tmp
name: tmp-volume
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/hostname": m-k8s
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
volumes:
- name: tmp-volume
emptyDir: {}
```
이제 마스터 노드에 배포된 대시보드로 접근해 보겠습니다.
웹 브라우저에 192.168.0.10:31000으로 접속합니다.
초기 접속 화면에서는 대시보드를 접속할 떄 허용된 사용자가 접근하는 데 필요한 인증 방법을 선택할 수 있습니다.
사용자에게 서비스 어카운트를 제공한 후 토큰을 생성해 사용하게 하거나,
적절한 쿠버네티스 구성 파일(kubeconfig)을 가진 사용자만 접근하게 하는 것이 일반적입니다.
하지만 실습에서는 간단하게 하기 위해 대시보드의 인증 과정을 생략하겠습니다.
생략버튼을 눌러서 인증과 관련된 설정을 생략합니다.
그러면 개요(Overview) 페이지 화면을 확인할 수 있습니다.
(만약 이와 다른 화면이 출력된다면 이미 배포된 파드 또는 서비스가 존재하기 때문입니다.)
개요 페이지의 각 요소들은 다음과 같은 기능을 제공합니다.
1. 네임 스페이스 : 대시보드에서 작업을 수행할 네임스페이를 선택합니다.
2. 검색창 : 입력한 검색어에 해당하는 오브젝트를 검색할 수 있습니다.
3. 오브젝트 목록 : 대시보드에서 조회할 수 있는 오브젝트의 목록을 표시합니다.
4. 시각화 패널 : 3에서 선택한 오브젝트의 목록과 설정 등을 표시하는 영역입니다.
5. 오브젝트 생성 : 웹 UI를 통해 오브젝트를 만들 수 있는 화면으로 이동합니다. 오브젝트 생성 메뉴로 이동 시 오브젝트를 만들 수 있는 생성, 삭제, 조회 등 적절한 역할 바인딩(rolebinding)이 해당 접속 계정에 필요합니다.
6. 알림 : 쿠버네티스 클러스터에서 발생한 알림을 확인할 수 있습니다.
7. 사용자 정보 : 현재 접속한 사용자 정보를 확인할 수 있습니다.
대시보드의 설치 및 구동을 완료했습니다.
편의를 위해 몇 가지 사항을 생략했지만, 정상적인 설치 단계를 거치더라도 다른 대시보드 도구보단 쉽게 설치하고 구동할 수 있습니다.
이제 설치한 대시보드로 좀 더 직관적이고 편리하게 쿠버네티스의 오브젝트를 생성해 보겠습니다!
그동안 kubectl 명령으로 쿠버네티스 클러스터에 디플로이먼트나 서비스 등을 구성했습니다.
kubectl 명령을 사용하려면 서버에 접속하고 오브젝트를 생성하는 데 필요한 옵션을 어느 정도 숙지해야 합니다.
그러나 대시보드는 서버에 접속하거나 명령어를 입력할 필요 없이 사용자가 화면을 보면서
필요한 정보를 입력하면 디플로이먼트와 서비스를 생성할 수 있는 기능을 제공합니다.
대시보드 개요 페이지에서 우측 상단에 있는 (+) 모양의 오브젝트 생성 버튼을 눌러 새로운 오브젝트를 생성하는 메뉴로 이동합니다.
오브젝트를 생성하는 화면이 나오면 서식을 통해 생성을 선택합니다.
사용자는 사전 지식이 없더라도 화면 안내에 따라 필요한 항목이 무엇인지 알 수 있습니다.
오브젝트가 생성되면 화면이 다음과 같은 개요 페이지로 전환됩니다.
왼쪽 메뉴에서 디플로이먼트를 선택하면 시각화 패널에서 디플로이먼트 목록과 상태를 확인할 수 있습니다.
대시보드로 배포한 디플로이먼트가 실제로 쿠버네티스 클러스터에 배포됐는지 확인해보겠습니다.
kubectl get deployment echo-ip
kubectl get svc echo-ip
명령으로 ClusterIP 타입의 서비스가 생성됐는지 확인합니다.
대시보드는 SSH 접속이나 별도의 도구 설치 없이도 웹에서 오브젝트 상태를 확인하고 관리할 수 있습니다.
다만 대시보드는 쿠버네티스 클러스터 내부에 설치되기 때문에 여러 쿠버네티스 클러스터를 한 번에 관리하거나 좀 더 세부적인 배포 설정을 하기는 어렵습니다.
따라서 다양한 기능을 제공하는 대시보드를 원한다면 렌즈(Lens), 옥탄트(Octant), 폴라리스(Polaris)와 같은 전문적인 대시보드 도구를 설치해야 합니다.
또한 프로메테우스와 그라파나 등의 특화된 도구를 사용하는 것도 좋은 방법입니다.
사용자 환경에 따라 도구를 선택해 구성할 수 있다는 것이 쿠버네티스 가장 큰 장점 중 하나입니다!
시작하기에 앞서 컴퓨터에 충분한 용량을 확보하고 진행하시길 바랍니다. 가상 머신을 추가로 여러개 구성하다보니 용량 부족시 컴퓨터가 다운되고, 설치가 정상적으로 진행되지 않는 문제가 있었습니다.
쿠버네티스 클러스터를 구성하는 대표적인 도구가 4가지(kubeadm, kops, KRIB, kubespray)가 있습니다.
그리고 이 책에서는 kubeadm으로 쿠버네티스 클러스터를 구성했습니다.
하지만 kubeadm은 따로 설정해야 하는 사항이 많습니다.
그래서 실무에서는 좀 더 편리한 방법을 사용하는데, 그 중 하나가 지금 소개할 kubespray입니다!
kubespray는 자동화 도구 중에 가장 많이 사용하는 앤서블을 이용해 쿠버네티스 클러스터를 자동으로 구성하게 합니다.
kubespray는 다음과 같이 설치하는 데 필요한 요구 사항이 있습니다.
kubespray로 쿠버네티스 클러스터를 구성하려면 서버가 필요합니다.
우선 가상 머신을 구성해 보겠습니다.
로컬 환경에 git glone해놓은 레지스트리(/Users/noharam/Desktop/k8s/app/C.kubespray)에서 vagrant up을 실행해 kubespray를 실행할 가상 머신을 설치합니다.
가상 머신이 모두 설치되면 다음과 같이 Terminus에서 모든 가상 머신에 접속합니다.
./auto_pass.sh
를 실행해 가상 머신 간의 교차 인증(Known_hosts, authorized_keys)를 진행합니다../auto_pass.sh
를 실행해줍니다.(총 9번 실행해야겠죠?, 그러려면 위 과정에서 먼저 9개의 호스트에 먼저 접속해놔야겠죠?)kubespray는 사용자가 원하는 형태의 쿠버네티스 클러스터를 구성하도록 다음 4가지 사항을 선택할 수 있습니다.
kubespray는 여러 개의 마스터와 etcd를 구성할 수 있어 이에 대한 수량을 지정할 수 있습니다.
여기서는 다음과 같이 미리 작성된 파일을 실행해 마스터 노드 3개, etcd와 워커 노드 6개, calico 네트워크 플러그인을 설치하겠습니다.
[all]
m11-k8s ansible_host=192.168.1.11 ip=192.168.1.11
m12-k8s ansible_host=192.168.1.12 ip=192.168.1.12
m13-k8s ansible_host=192.168.1.13 ip=192.168.1.13
w101-k8s ansible_host=192.168.1.101 ip=192.168.1.101
w102-k8s ansible_host=192.168.1.102 ip=192.168.1.102
w103-k8s ansible_host=192.168.1.103 ip=192.168.1.103
w104-k8s ansible_host=192.168.1.104 ip=192.168.1.104
w105-k8s ansible_host=192.168.1.105 ip=192.168.1.105
w106-k8s ansible_host=192.168.1.106 ip=192.168.1.106
[etcd]
m11-k8s
m12-k8s
m13-k8s
[kube-master]
m11-k8s
m12-k8s
m13-k8s
[kube-node]
w101-k8s
w102-k8s
w103-k8s
w104-k8s
w105-k8s
w106-k8s
[calico-rr]
[k8s-cluster:children]
kube-master
kube-node
calico-rr
이 파일은 m11-k8s 노드 안에 들어있습니다.(ls로 확인)
ansible-playbook kubespray/cluster.yml -i ansible_hosts.ini
kubectl get nodes
로 쿠버네티스 클러스터 구성을 확인합니다.이렇게 쿠버네티스 클러스터가 kubespray로 자동 구성됐습니다!
쿠버네티스를 운영하면서 컨테이너와 관련해 논의할 사항이 생겼을 때, 컨테이너에 대한 이해가 없다면 깊게 논의하기가 어렵습니다.
또한 PaaS(Platform as a Service)와 같이 컨테이너를 기반으로 플랫폼을 제공하는 서비스를 만들 때는
컨테이너 런타임을 직접 관리해야 하기 때문에 동작 원리도 알아둬야 합니다!
그러므로 쿠버네티스가 컨테이너를 다루는 과정과 컨테이너의 PID가 무엇인지 살펴보고
도커가 아닌 다른 방법으로 컨테이너를 생성해 보며 컨테이너를 좀 더 깊게 알아보겠습니다.
앞선 포스팅에서 파드의 생명 주기로 쿠버네티스 구성 요소 살펴보기에서
쿠버네티스의 구성 요소를 중심으로 파드가 생성되는 과정을 살펴봤습니다.
이번에는 파드 생성 과정을 컨테이너 중심으로 살펴보겠습니다.
도커의 버전마다 약간씩 다르므로 멀티 스테이지 빌드가 되는 docker-ce 18.09.9를 기준으로 알아봅시다.
사용자는 kube-apiserver의 URL로 요청을 전달하거나 kubectl을 통해 명령어를 입력해 kube-apiserver에 파드를 생성하는 명령을 내립니다.
파드를 생성하는 명령은 네트워크를 통해 kubelet으로 전달됩니다.
ls /etc/kubernetes/pki/apiserver-kubelet-clinet.*
cat /var/lib/kubelet/config.yaml | grep clientCAFile
kubelet에서 요청을 검증하고 나면 컨테이너디에 컨테이너를 생성하는 명령을 내립니다.
컨테이너디는 containerd-shim이라는 자식(child) 프로세스를 생성해 컨테이너를 관리합니다.
ps -ef | head -n 1 && ps -ef | grep -v auto | grep containerd
containerd가 생성한 containerd-shim 프로세스는 컨테이너를 조작합니다. 실제로 containerd-shim이 runC 바이너리 실행 파일을 호출해 컨테이너를 생성합니다.
사용자가 파드를 생성하려고 보낸 명령이 어떤 과정을 거치면서 전달되는지 살펴보았습니다.
기억나실지 모르겠지만 이전 포스팅에서 컨테이너의 PID는 항상 1을 가진다고 했습니다.
하지만 containerd와 containerd-shim 어디에도 1은 보이지 않습니다. 어떻게 된걸까요?
컨테이너를 이해하려면 이 관계를 좀 더 살펴봐야 합니다.
리눅스 운영 체제의 PID를 공부해 보면 알겠지만 PID 1은 커널이 할당하는 첫 번째 PID라는 의미의 특수한 PID입니다.
일반적으로 init 또는 systemd에 PID 1이 할당되며 시스템 구동에 필요한 프로세스들을 띄우는 매우 중요한 역할을 합니다.
PID 1번 이외에도 PID 0번 swapper, PID 2번 kthreadd 등이 미리 예약된 PID를 가집니다.
그런데 컨테이너의 PID 1번은 컨테이너에서 실행된 애플리케이션인 nginx 프로세스가 가집니다. 왜일까요?
다시 한 번 예약된 PID의 역할을 읽어 봅시다. 시스템을 구동시키는 역할입니다.
그런데 컨테이너는 이미 구동된 시스템, 즉 커널 위에서 동작한다고 배웠습니다.
뭔가 앞뒤가 맞는 느낌이 들죠?
컨테이너는 운영 체제 시스템을 구동시킬 필요가 없이 바로 동작하기 때문에 시스템에 예약된 PID 1번이 할당되지 않은 상태라서 최초 실행자 nginx에게 할당할 수 있습니다.
그래서 특수한 PID 1번은 컨테이너 시계에서 컨테이너가 실행하는 첫 애플리케이션에게 할당해 사용합니다.
도커로 생성한 컨테이너 PID와 컨테이너 내부의 PID 1번이 연결돼 있는지 실습으로 확인해 봅시다!
이번 실습에서 생성할 nginx의 동작을 정확하게 확인하기 위해 ps -ef | grep -v auto | grep nginx
명령을 실행해 nginx와 관련된 프로세스가 호스트에 있는지 확인합니다.
이제 nginx 컨테이너를 구동하고 nginx와 관련된 프로세스를 다시 확인합니다.
docker run -d nginx
명령을 수행하는 과정에서 이미지가 필요하다면 자동으로 내려받게 됩니다.(library/nginx 에서)ps -ef | grep -v auto | grep nginx
을 실행한 결과 PID가 27021인 프로세스가 나타납니다.호스트에서 구동 중인 nginx 프로세스를 확인했으니 컨테이너 내부의 nginx 프로세스를 확인해 봅시다.
/proc/<PID>/
디렉터리에 보관하며 /proc/<PID>/exe
는 해당 <PID>
를 가지는 프로세스를 생성하기 위해 실제로 실행된 명령어를 보여줍니다.docker ps
로 구동중인 nginx의 CONTAINER ID를 확인하고 아래 명령어에 활용해주세요!docker exec 240a ls -l /proc/1/exe
docker exec <컨테이너 ID>
를 입력하지 않으면 호스트의 /proc/1/exe의 정보를 확인하는 것이 됩니다(커널을 구동시키는 관련 디렉터리 정보가 나타나겠죠?). 우리는 도커 내부의 컨테이너를 확인하고 싶은 것이니 해당 명령이 추가되어야 합니다.호스트와 컨테이너 내부에서 nginx가 구동되는 것을 확인했습니다. 그렇다면 이 두개가 동일한 프로세스라는 것을 확인하면 됩니다.(호스트 PID 27021과 컨테이너 PID 1번이 동일하다는 것)
/proc/<PID>/cgroup
의 내용을 cat으로 조회하면 알 수 있습니다.240a
로 시작하는 내용들이 서로 동일한 것으로 모아 두 프로세스는 관련이 있다는 것을 추측할 수 있습니다.cat /proc/27021/cgroup
docker exec 240a cat /proc/1/cgroup
이 프로세스가 정말 동일한지 PID 27021번을 강제 종료하고 컨테이너 PID 1번의 상태를 확인해 보곘습니다.
kill -9 27021
cat /proc/27021/cgroup
docker exec 240a cat /proc/1/cgroup
동일한 프로세스가 호스트와 컨테이너 내부에서 서로 다른 PID를 가지면서 컨테이너 내부에서는 독립된 것처럼 PID 1번 프로세스로 동작할 수 있는 이유는
컨테이너가 리눅스의 네임스페이스 기술을 활용하기 때문입니다.
네임스페이스는 호스트명, 네트워크, 파일 시스템 마운트, 프로세스 간 통신, 사용자 ID, PID 등의 자원을 격리할 수 있는 기술입니다.
네임스페이스로 격리된 내부에서는 외부가 보이지 않기 때문에 컨테이너 내부는 독립된 공간처럼 느껴집니다.
이때 네임스페이스로 격된 프로세스에 메모리, CPU, 네트워크, 장치 입출력 등의 사용량을 할당하고 제한하는 데 앞에서 설명한 씨그룹 기술을 사용합니다.
그리고 이러한 격리 기술을 쉽게 사용할 수 있게 하는 것이 도커입니다.
그런데 만약에 도커를 사용하지 않는다면 얼마나 번거롭게 컨테이너를 만들어야 할까요?
직접 컨테이너를 만들어보면서 확인해 봅시다.
앞에서 컨테이너를 만들 떄 실제로는 runC라는 실행 바이너리를 통해 만든다고 했는데,
runC는 무엇일까요?
runC는 오픈 컨테이너 이니셔티브(OCI, Open Container Initiative, 컨테이너 형식과 런타임에 대해 개방된 업계 표준을 만들기 위한 단체)에서 만든 컨테이너 생성 및 관리를 위한 표준 규격입니다.
컨테이너디, 크라이오 등의 다양한 컨테이너 런타임들이 내부적으로 runC를 활용해 표준 규격을 따르는 컨테이너를 생성하고 있습니다.
이 때문에 컨테이너 런타임을 분류할 때 쿠버네티스나 도커 명령을 받아들이는 컨테이너디나 크라이오 같은 구성 요소를 고수준(High-level) 컨테이너 런타임,
실제로 컨테이너를 조작하기 위해 리눅스에 명령을 내리는 runC를 저수준(Low-lebel) 컨테이너 런타임으로 분류합니다.
그러면 지금까지 사용했던 도커와 같은 고수준의 컨테이너 런타임이 아닌 저수준의 컨테이너 런타임을 통해
컨테이너를 생성해 보며 고수준의 컨테이너 런타임이 얼마나 효과적으로 컨테이너를 생성 및 관리하게 도와줬는지 체감해 봅시다.
실습에서는 가능한 복잡하지 않게 씨그룹을 이용한 자원 할당 및 제한에 관해서는 다루지 않고, 네임스페이스를 이용해 프로세스와 네트워크를 분리하는 수준에서만 진행하겠습니다.
본 게시물은 "컨테이너 인프라 환경 구축을 위한 쿠버네티스/도커 - 조훈,심근우,문성주 지음(2021)" 기반으로 작성되었습니다.