컨테이너 인프라 환경
리눅스 운영 체제의 커널 하나에서 여러 개의 컨테이너가 격리된 상태로 실행되는 인프라 환경
컨테이너
: 하나 이상의 목적을 위해 독립적으로 작동하는 프로세스
: 친구와 대화를 주고받는 메신저 프로그램이나 음악 감상 프로그램을 컨테이너로 구현할 수 있음
개인환경
1명의 관리자가 다양한 응용프로그램을 사용함
-> 각각의 프로그램을 컨테이너로 구현할 필요가 거의 없음
기업 환경
다수의 관리자가 수백 또는 수천 대의 서버를 함께 관리함
-> 일관성을 유지하는 것이 매우 중요함
가상화 환경에서는 각각의 가상 머신이 모두 독립적인 운영 체제 커널을 가지고 있어야 함
-> 그만큼 자원을 더 소모해야 하고 성능이 떨어짐
컨테이너 인프라 환경은 운영 체제 커널 하나에 컨테이너 여러 개가 격리된 형태로 실행됨
-> 자원을 효율적으로 사용할 수 있고 거치는 단계가 적어서 속도도 훨씬 빠름
컨테이너 오케스트레이션을 위한 솔루션
오케스트레이션(Orchestration)
: 복잡한 단계를 관리하고 요소들의 유기적인 관계를 미리 정의해 손쉽게 사용하도록 서비스를 제공하는 것
: 다수의 컨테이너를 유기적으로 연결, 실행, 종료, 상태를 추적하고 보존
: 컨테이너를 안정적으로 사용할 수 있게 만들어주는 것
[k8s 의미]
- k8s는 쿠버네티스(Kubernetes)의 약어
- k8(ubernete, 8글자)s의 형식으로 만들어짐
- 쿠버네티스는 그리스어로 도선사(pilot, 배를 수로로 안전하게 안내하는 사람)나
조타수(helmsman, 배의 키를 조정해 올바른 방향으로 나아가게 하는 사람)를 의미함
퍼블릭 클라우드 업체에서 제공하는 관리형 쿠버네티스
인 EKS(Amazon Elastic Kubernetes Service), AKS(Azurc Kubernetes Service), GKE(Google Kubernetes Engine) 등을 사용
-> 구성이 이미 다 갖춰져 있고 마스터 노드를 클라우드 업체에서 관리하기 때문에 학습용으로는 적합하지 않음
수세의 Rancher, 레드햇의 OpenShift와 같은 플랫폼에서 제공하는 설치형 쿠버네티스
를 사용
-> 유료라 쉽게 접근하기 어려움
사용하는 시스템에 쿠버네티스 클러스티를 자동으로 구성해주는 구성형 쿠버네티스
를 사용
-> kubeadm, kops(Kubernetes Operations), KRIB(Kubernetes Rebar Intergrated Bootstrap), kubespray
-> kubeadm이 가장 널리 알려져 있음
-> kubeadm은 사용자가 변경 하기도 수월하고, 온프레미스(On-Premises)와 클라우드를 모두 지원하며, 배우기도 쉬움
kubeadm으로 구성
서버 노드는 가상 머신을 이용하여 온프레미스에 가깝게 구성
설치되는 과정을 베이그런트로 자동화
: 베이그런트 프로비저닝을 위한 정보를 담고 있는 메인 파일
cmd 창(Vagrantfile이 있는 경로)에서 vagrant up 명령을 입력
-> 현재 호스트 내부에 Vagrantfile에 정의된 가상 머신들을 생성
-> 생성한 가상 머신에 쿠버네티스 클러스트를 구성하기 위한 파일들을 호출해 쿠버네티스 클러스터를 자동으로 구성
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
N = 3 # max number of worker nodes
Ver = '1.18.4' # Kubernetes Version to install
#=============#
# Master Node #
#=============#
config.vm.define "m-k8s" do |cfg|
cfg.vm.box = "sysnet4admin/CentOS-k8s"
cfg.vm.provider "virtualbox" do |vb|
vb.name = "m-k8s(github_SysNet4Admin)"
vb.cpus = 2
vb.memory = 3072
vb.customize ["modifyvm", :id, "--groups", "/k8s-SgMST-1.13.1(github_SysNet4Admin)"]
end
cfg.vm.host_name = "m-k8s"
cfg.vm.network "private_network", ip: "192.168.1.10"
cfg.vm.network "forwarded_port", guest: 22, host: 60010, auto_correct: true, id: "ssh"
cfg.vm.synced_folder "../data", "/vagrant", disabled: true
cfg.vm.provision "shell", path: "config.sh", args: N
cfg.vm.provision "shell", path: "install_pkg.sh", args: [ Ver, "Main" ]
cfg.vm.provision "shell", path: "master_node.sh"
end
#==============#
# Worker Nodes #
#==============#
(1..N).each do |i|
config.vm.define "w#{i}-k8s" do |cfg|
cfg.vm.box = "sysnet4admin/CentOS-k8s"
cfg.vm.provider "virtualbox" do |vb|
vb.name = "w#{i}-k8s(github_SysNet4Admin)"
vb.cpus = 1
vb.memory = 2560
vb.customize ["modifyvm", :id, "--groups", "/k8s-SgMST-1.13.1(github_SysNet4Admin)"]
end
cfg.vm.host_name = "w#{i}-k8s"
cfg.vm.network "private_network", ip: "192.168.1.10#{i}"
cfg.vm.network "forwarded_port", guest: 22, host: "6010#{i}", auto_correct: true, id: "ssh"
cfg.vm.synced_folder "../data", "/vagrant", disabled: true
cfg.vm.provision "shell", path: "config.sh", args: N
cfg.vm.provision "shell", path: "install_pkg.sh", args: Ver
cfg.vm.provision "shell", path: "work_nodes.sh"
end
end
end
5번째 줄 : N = 3 # max number of worker nodes
: 쿠버네티스에서 작업을 수행할 워커 노드의 수
: 24번째 줄, 46번째 줄에서 config.sh로 넘김
: 사용자가 워커 노드의 개수를 직접 조절할 수 있음
6번째 줄 : Ver = '1.18.4' # Kubernetes Version to install
: 쿠버네티스 버전을 사용자가 선택할 수 있음
: 다른 쿠버네티스 버전을 사용하기 위해 값을 변경하면 됨
25번째 줄 : cfg.vm.provision "shell", path: "install_pkg.sh", args: [ Ver, "Main" ]
: 쿠버네티스 버전 정보(Ver)와 Main이라는 문자를 install_pkg.sh로 넘김
: Ver 변수는 각 노드에 해당 버전의 쿠버네티스 버전을 설치함
: Main 문자는 install_pkg.sh에서 조건문으로 처리해 마스터 노드에만 이 책의 전체 실행 코드를 내려받게 함
26번째 줄 : cfg.vm.provision "shell", path: "master_node.sh"
: 쿠버네티스 마스터 노드를 위한 master_node.sh 코드 추가
48번째 줄 : cfg.vm.provision "shell", path: "work_nodes.sh"
워커 노드를 위한 work_nodes.sh 코드 추가
: kubeadm으로 쿠버네티스를 설치하기 위한 사전 조건을 설정하는 스크립트 파일
#!/usr/bin/env bash
# vim configuration
echo 'alias vi=vim' >> /etc/profile
# swapoff -a to disable swapping
swapoff -a
# sed to comment the swap partition in /etc/fstab
sed -i.bak -r 's/(.+ swap .+)/#\1/' /etc/fstab
# kubernetes repo
gg_pkg="packages.cloud.google.com/yum/doc" # Due to shorten addr for key
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://${gg_pkg}/yum-key.gpg https://${gg_pkg}/rpm-package-key.gpg
EOF
# Set SELinux in permissive mode (effectively disabling it)
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# RHEL/CentOS 7 have reported traffic issues being routed incorrectly due to iptables bypassed
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
modprobe br_netfilter
# local small dns & vagrant cannot parse and delivery shell code.
echo "192.168.1.10 m-k8s" >> /etc/hosts
for (( i=1; i<=$1; i++ )); do echo "192.168.1.10$i w$i-k8s" >> /etc/hosts; done
# config DNS
cat <<EOF > /etc/resolv.conf
nameserver 1.1.1.1 #cloudflare DNS
nameserver 8.8.8.8 #Google DNS
EOF
echo 'alias vi=vim' >> /etc/profile
swapoff -a
sed -i.bak -r 's/(.+ swap .+)/#\1/' /etc/fstab
gg_pkg="packages.cloud.google.com/yum/doc" # Due to shorten addr for key
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://${gg_pkg}/yum-key.gpg https://${gg_pkg}/rpm-package-key.gpg
EOF
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
modprobe br_netfilter
IP 마스커레이드
: 커널에서 제공하는 NAT(Network Address Translation) 기능
echo "192.168.1.10 m-k8s" >> /etc/hosts
for (( i=1; i<=$1; i++ )); do echo "192.168.1.10$i w$i-k8s" >> /etc/hosts; done
cat <<EOF > /etc/resolv.conf
nameserver 1.1.1.1 #cloudflare DNS
nameserver 8.8.8.8 #Google DNS
EOF
: 클러스터를 구성하기 위해 가상머신에 설치되어야 하는 의존성 패키지를 명시함
: 실습에 필요한 소스 코드를 특정 가상머신(m-k8s) 내부에 내려받도록 설정되어 있음
#!/usr/bin/env bash
# install packages
yum install epel-release -y
yum install vim-enhanced -y
yum install git -y
# install docker
yum install docker -y && systemctl enable --now docker
# install kubernetes cluster
yum install kubectl-$1 kubelet-$1 kubeadm-$1 -y
systemctl enable --now kubelet
# git clone _Book_k8sInfra.git
if [ $2 = 'Main' ]; then
git clone https://github.com/sysnet4admin/_Book_k8sInfra.git
mv /home/vagrant/_Book_k8sInfra $HOME
find $HOME/_Book_k8sInfra/ -regex ".*\.\(sh\)" -exec chmod 700 {} \;
fi
6번째 줄
yum install git -y
: 깃허브에서 코드를 내려받을 수 있게 깃(git)을 설치함
9번째 줄
yum install docker -y && systemctl enable --now docker
: 쿠버네티스를 관리하는 컨테이너를 설치하기 위해 도커를 설치하고 구동함
12~13번째 줄
: 쿠버네티스를 구성하기 위해 첫 번째 변수($1=Ver='1.18.4')로 넘겨받은 1.18.4 버전의 kubectl, kubelet, kubeadm을 설치하고 kubelet을 시작함
yum install kubectl-$1 kubelet-$1 kubeadm-$1 -y
systemctl enable --now kubelet
if [ $2 = 'Main' ]; then
git clone https://github.com/sysnet4admin/_Book_k8sInfra.git
mv /home/vagrant/_Book_k8sInfra $HOME
find $HOME/_Book_k8sInfra/ -regex ".*\.\(sh\)" -exec chmod 700 {} \;
fi
: 1개의 가상 머신(m-k8s)을 쿠버네티스 마스터 노드로 구성하는 스크립트
: 쿠버네티스 클러스터를 구성할 때 꼭 선택해야 하는 컨테이너 네트워크 인터페이스(CNI, Container Network Interface)도 함께 구성
#!/usr/bin/env bash
# init kubernetes
kubeadm init --token 123456.1234567890123456 --token-ttl 0 \
--pod-network-cidr=172.16.0.0/16 --apiserver-advertise-address=192.168.1.10
# config for master node only
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# config for kubernetes's network
kubectl apply -f \
https://raw.githubusercontent.com/sysnet4admin/IaC/master/manifests/172.16_net_calico.yaml
kubeadm init --token 123456.1234567890123456 --token-ttl 0 \
--pod-network-cidr=172.16.0.0/16 --apiserver-advertise-address=192.168.1.10
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
kubectl apply -f \
https://raw.githubusercontent.com/sysnet4admin/IaC/master/manifests/172.16_net_calico.yaml
: 3대의 가상 머신(w1-k8s, w2-k8s, w3-k8s)에 쿠버네티스 워커 노드를 구성하는 스크립트
: 마스터 노드에 구성된 클러스터에 조인이 필요한 정보가 모두 코드화 되어있음
: 스크림트를 실행하기만 하면 편하게 워커 노드로서 쿠버네티스 클러스터에 조인됨
#!/usr/bin/env bash
# config for work_nodes only
kubeadm join --token 123456.1234567890123456 \
--discovery-token-unsafe-skip-ca-verification 192.168.1.10:6443
: kubeadm을 이용해 쿠버네티스 마스터 노드에 접속함
: 연결에 필요한 토큰은 기존에 마스터 노드에서 생성한 123456.1234567890123456을 사용함
: 간단하게 구성하기 위해 --discovery-token-unsafe-skip-ca-verification
으로 인증을 무시함
: API 서버 주소로 기본 포트 번호인 6443번 포트에 접속하도록 설정
vagrant up
명령 실행kubectl get nodes
명령으로 정상적으로 생성, 연결되었는지 확인kubectl get pods --all-namespaces
명령
: 설치된 구성 요소 확인
: 기본 네임스페이스인 default 외에 모든 것을 표시
: 네임스페이스에서 파드를 수집해 보여줌
: 구성 요소들은 파드 형태로 이루어져 있음
▼ 쿠버네티스의 구성 요소의 유기적인 연결 관계 & 관리자나 개발자가 파드 배포 명령을 수행했을 때 실행되는 순서
kubectl
: 쿠버네티스 클러스터에 명령을 내리는 역할
: 다른 구성 요소들과 다르게 바로 실행되는 명령 형태인 바이너리(binary)로 배포됨 -> 마스터 노드에 있을 필요는 없음
: API 서버와 주로 통신하므로 마스터 노드에 구성함
API 서버
: 쿠버네티스 클러스터의 중심 역할을 하는 통로
: 주로 상태 값을 저장하는 etcd와 통신
: 다른 요소들도 API 서버를 중심에 두고 통신하므로 API 서버의 역할이 매우 중요
: 모든 직원과 상황을 관리하고 목표를 설정하는 관리자 역할
etcd
: 구성 요소들의 상태 값이 모두 저장되는 곳
: 다른 구성 요소들은 상태 값을 관리하지 않음
: etcd 정보만 백업되어 있으면 긴급한 장애 상황에서도 클러스터를 복구할 수 있음
: 분산 저장인 가능한 key-value 저장소 -> 복제하여 여러 곳에 저장해두면 가용성 확보 가능
: 회사의 관리자가 모든 보고 내용을 기록하는 노트
etcd 의미 : 리눅스의 구성 정보를 가지고 있는 etc 디렉터리와 distributed의 합성어
-> 구성 정보를 퍼뜨려 저장하겠다는 의미
컨트롤러 매니저
: 쿠버네티스 클러스터의 오브젝트 상태를 관리
: 워커 노드에서 통신이 되지 않는 경우, 상태 체크와 복구가 이루어짐
컨트롤러 종류
스케줄러
: 노드의 상태와 자원, 레이블, 요구 조건 등을 고려하여 파드를 어떤 워커 노드에 생성할 것인지 결정하고 할당함
: 파드를 조건에 맞는 워커 노드에 지정하고, 파드가 워커 노드에 할당되는 일정을 관리
kubelet
: 파드의 구성 내용(PodSpec)을 받아서 컨테이너 런타임으로 전달
: 파드 안의 컨테이너들이 정상적으로 작동하는지 모니터링
컨테이너 런타임(CRI, Container Runtime Interface)
: 파드를 이루는 컨테이너의 실행을 담당함
: 파드 안에서 다양한 종류의 컨테이너가 문제 없이 작동하게 만드는 표준 인터페이스
파드(pod)
: 한 개 이상의 컨테이너로 단일 목적의 일을 하기 위해서 모인 단위
: 웹서버 역할을 할 수도 있고 로그나 톄이터를 분석할 수도 있음
: 언제라도 죽을 수 있는 존재
-> 여러 대안을 디자인
네트워크 플러그인
: 클러스터의 통신을 위해서 네트워크 플러그인을 선택하고 구성해야 함
: 일반적으로 CNI로 구성
: 캘리코, 플래널, 실리움, 큐브 라우터, 로마나, 위브넷, Canal
CNI(Container Network Interface) : 컨테이너의 네트워크 안전성과 확장성을 보장하기 위해 개발됨
CoreDNS
: 빠르고 유연한 DNS 서버
: 클러스터에서 도메인 이름을 이용해 통신하는 데 사용함
: 실부에서 쿠버네티스 클러스터를 구성하여 사용할 때는 IP보다 CoreDNS를 사용하는 것이 일반적임
홈페이지
kube-proxy
: 파드가 위치한 노드에 kube-proxy를 통해 파드가 통신할 수 있는 네트워크를 설정함
: 실제 통신은 br_netfilter와 iptables로 관리함
파드
: 배포된 파드에 접속하고 필요한 내용을 전달받음
: 파드가 어느 워커 노드에 위치하는지 신경 쓰지 않아도 됨
쿠버네티스의 장점
: 구성 요소마다 하는 일이 명확하게 구분되어 각자의 역할만 충실하게 수행하면 클러스터 시스템이 안정적으로 운영됨
: 문제가 발생했을 때 어느 부분에서 문제가 발생했는지 디버깅하기 쉬움
kubectl을 통해 API 서버에 파드 생성을 요청함
(업 데이트가 있을 때마다 매번) API 서버에 전달된 내용이 있으면 API 서버는 etcd에 전달된 내용을 모두 기록해 클러스터의 상태 값을 최신으로 유지함
-> 각 요소가 상태를 업데이트할 매마다 모두 API 서버를 통해 etcd에 기록됨
API 서버에 파드 생성이 요청된 것을 컨트롤러 매니저가 인지하면 컨트롤러 매니저는 파드를 생성하고, 이 상태를 API 서버에 전달함
(아직 어떤 워커 노드에 파드를 적용할지는 결정되지 않은 상태로 파드만 생성됨)
API 서버에 파드가 생성됐다는 정보를 스케줄러가 인지함
스케줄러는 생성된 파드를 어떤 워커 노드에 적용할지 조건을 고려해 결정하고 해당 워커 노드에 파드를 띄우도록 요청함
API 서버에 전달된 정보대로 지정한 워커 노드에 파드가 속해 있는지 스케줄러가 kubelet으로 확인함
kubelet에서 컨테이너 런타임으로 파드 생성을 요청함
파드가 생성됨
파드가 사용 가능한 상태가 됨
선언적인 시스템 구조 : 추구하는 상태를 선언하면 현재 상태와 맞는지 점검하고 그것에 맞추려고 노력하는 구조
API 서버에 추구하는 상태를 선언함
-> 다른 요소들은 API 서버에 와서 현재 상태와 비교하면서 상태를 변경하려고 함
API는 현재 상태 값을 가지고 있고 이를 보존하기 위해 etcd가 필요함
마스터 노드에 위치할 필요 없음
외부에서 쿠버네티스 클러스터에 명령을 내릴 수도 있음
슈퍼푸티 w3-k8s 터미널 접속
kubectl get nodes
명령어 실행
-> 노드들에 대한 정보가 표시되지 않음
-> kubectl이 정보를 알 수 없기 때문
클러스터의 정보(/etc/kubernetes/admin.conf
)를 마스터 노드에서 scp 명령으로 w3-k8s의 현재 디렉터리(.
)에 받아옴
접속 기록이 없기 때문에 known_hosts로 저장하도록 yes 입력
접속 암호 vagrant도 입력
scp root@192.168.1.10:/etc/kubernetes/admin.conf .
kubectl get nodes
명령에 추가로 정보를 입력받는 옵션(--kubeconfig
)과 마스터 노드에서 받아온 admin.conf를 입력하고 실행
kubectl get nodes --kubeconfig admin.conf
쿠버네티스에 파드의 생성과 상태 관리 및 복구 담당
kubelet에 문제가 생기면 파드가 정상적으로 관리되지 않음
m-k8s(마스터 노드)에서 kubectl create -f ~/_Book_k8sInfra/ch3/3.1.6/nginx-pod.yaml
명령으로 nginx 웹 서버 파드 배포
-f
: filename을 의미함
kubectl get pod
명령으로 패보된 파드 상태 확인
kubectl get pods -o wide
명령으로 파드가 배포된 워커 노드 확인
-o
: output의 약어로 출력을 특정 형식으로 해 주는 옵션
wide
: 제공되는 출력 형식 중에서 출력 정보를 더 많이 표시해 주는 옵션
배포된 노드에 접속해 systemctl stop kubelet
으로 kubelet 서비스를 멈춤
m-k8s에서 kubectl get pod
로 상태 확인
kubectl delete pod nginx-pod
명령으로 파드 삭제
기다리다가 ^C로 명령 중지
다시 kubectl get pod
명령으로 상태 확인
-> nginx-pod를 삭제(Terminating)하는 중
-> kubelet이 작동하지 않는 상태라 파드가 삭제되지 않음
배포된 노드에서 systemctl start kubelet
명령으로 kubelet 복구
잠시 후에 m-k8s에서 kubectl get pod
명령으로 nginx-pod가 삭제되었는지 확인
파드의 통신을 담당
config.sh 파일에서 br_netfilter 커널 모듈을 적재하고 iptables를 거쳐 통신하도록 설정되어 있음
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
modprade br_netfilter
이 설정이 정상적으로 작동하지 않는다면(kube-proxy에 문제가 생긴다면) 어떻게 될지 확인해보도록 함
마스터 노드 m-k8s에서 파드 배포
kubectl create -f ~/_Book_k8sInfra/ch3/3.1.6/nginx-pod.yaml
kubectl get pods -o wide
명령으로 파드의 IP와 워커 노드 확인
curl(client URL)로 전 단계에서 확인한 파드의 IP로 nginx 웹 서버 메인 페이지 내용을 확인함
curl 172.16.103.129
(IP 다를 수 있음)
kube-proxy에 문제가 생기는 상황 만들기
배포된 노드에 접속해 modprobe -r br_netfilter
명령으로 파드가 위치한 워커 노드에서 br_netfilter 모듈 제거
-r
: remove 의미
systemctl restart network
명령으로 네트워크를 다시 시작해 변경된 내용 적용
m-k8s에서 다시 한 번 curl로 파드의 nginx 웹 서버 페이지 정보를 받아옴
curl 172.16.103.129
기다려도 종료가 안되면 ^C로 종료
kubectl get pods -o wide
로 파드 상태 확인
파드 노드 위치나 IP가 변경되지 않았는지, 작동 상태에 문제 없는지 확인
-> 모두 문제가 없음
-> kube-proxy가 이용하는 br_netfilter에 문제가 있어서 파드의 nginx 웹 서버와의 통신만이 정상적으로 이루어지지 않는 상태
(Connection timed out)
정상적으로 파드의 nginx 웹 서버 페이지 정보를 받아올 수 있는 상태로 만들기
워커 노드에서 modprobe br_netfilter
로 br_netfilter를 커널에 적재하고 시스템을 다시 시작함 reboot
m-k8s에서 파드 상태를 확인하면 RESTARTS가 1로 증가되고 IP가 변경됨
kubectl get pods -o wide
바뀐 IP로 curl 명령 실행하여 정보를 정상적으로 받아오는지 확인
배포된 파드 삭제
kubectl delete -f ~/_Book_k8sInfra/ch3/3.1.6/nginx-pod.yaml