跳到正文Lab 04: Container Orchestration with Kubernetes · Jimmy Huang's BlogLab 04: Container Orchestration with Kubernetes
本页目录· 81 条
这一页有部分内容暂时无法显示,其余内容完整。
Lab学后总结›Overview
在之前的实验中,我们讨论了容器化如何彻底改变了应用程序在其目标平台上的部署方式。Docker 和其他容器运行时环境主要用于在单台机器上部署容器。如果您拥有分布式环境或资源集群,则需要使用容器编排工具,例如 Kubernetes(也称为 K8s,因为 K 和 s 之间有 8 个字符) 或 Docker Swarm ,以自动化容器创建、部署、管理、监控、多租户支持等任务。 如今,容器编排引擎已成为云原生应用程序、持续集成 (CI) 和持续部署 (CD) 流水线以及众多其他 DevOps 任务的基石。
Goal
在本实验中,您将学习如何使用 kubeadm 创建和配置 Kubernetes 集群。虽然许多发行版中都提供了其他工具,但 kubeadm 是创建 Kubernetes 集群的原生方法。
接下来,您将使用 Kubernetes 集群创建不同类型的资源,从 Pod 到 Deployment 和服务。此外,您还将了解到在 Kubernetes 中扩展/缩减 Deployment 是多么轻松、快捷和高效,以及如何使用 Kubernetes 标签和资源选择器自动为服务执行负载均衡。
对于那些志存高远的学生 ,你们可以选择尝试用比较困难的方式安装 K8s,从而深入了解 K8s。
您需要使用您有权访问的任何云服务提供商(Nectar、Oracle Cloud、Azure、GCP 免费试用版)来创建 3 个虚拟机(1 个主节点和 2 个工作节点)。
主线
3 台 VM → 装运行环境 → 建 K8s 集群 → 创建 Pod/Deployment → 用 Service 暴露 → 观察扩缩容和负载均衡 → 再做存储持久化。
基础概念
一个简单类比
Dockerfile = 配方
Image = 总部统一半成品
Container = 某一家门店做出来的一杯奶茶
Pod = 一个工作台
Deployment = 店长,要求“始终保持 4 个工作台在工作”
Service = 前台取号口,顾客不需要知道后面具体哪个工作台在做
PVC/PV = 仓库申请单 / 真正的仓库空间
Task 1: 创建 3 台 VM 并安装 Kubernetes
概括
创建 3 台 Ubuntu 22.04 VM,规格 2 cores / 8GB RAM
命名为 k8s-master、k8s-worker1、k8s-worker2
打开需要的端口,至少实验文档先强调了 TCP 6443;Oracle 还要额外开 iptables 规则
用 SSH 连到 3 台机器
用 SCP 把 3 个安装脚本传上去
在所有节点安装 Docker
在所有节点安装 cri-dockerd
在所有节点安装 kubeadm、kubelet、kubectl
如果是 Oracle,要改 /etc/iptables/rules.v4 并恢复规则
只在 master 上执行 kubeadm init 初始化集群
在 master 配置 kubectl
在两个 worker 上执行 join 命令
在 master 上检查 kubectl get nodes
安装 Weave Net 网络插件
最后移除两个 worker 的公网 IP(安全考虑)
如果失败,可以 kubeadm reset 重来
如果标准安装始终不通,可以改用 K3s 继续后面任务
Step 1.1 在Project中启动VM instances
Step 1.2 创建VM instances
Machine configuration 参数填写
Name: k8s-master/k8s-worker1/k8s-worker2
Region: australia-southeast2 (Melbourne)
Zone: australia-southeast2-a
Series: E2
Machine type: e2-standard-2 (2 vCPU, 1 core, 8GB memory)
OS and storage选择Ubuntu 22.004 LTS
Step 1.2: 确定3台VM都有内网IP、外网IP、SSH
Step 1.3: 打开防火墙端口
Description: Kubernetes lab firewall rule for SSH, API server, NodePort and Weave
Direction of traffic: Ingress
Targets: All instances in the network
Source IPv4 range: 0.0.0.0/0
TCP: 22,6443,2379-2380,10250,10256,10257,10259,30000-32767,6783
Step 1.4:SSH 进入三台机器并确认内网 IP
返回VM instances,点击k8s-master右边的SSH,打开SSH页面
然后依次点击k8s-worker1, k8s-worker2的SSH打开三个窗口
Step 1.5:准备安装脚本
Step 1.6:在三台机器上安装 Docker
Step 1.7: 在三台机器上安装 cri-dockerd
Step 1.8: 在三台机器上安装 kubeadm / kubelet / kubectl
Step 1.9:在 master 上初始化 Kubernetes 集群
成功后显示Your Kubernetes control-plane has initialized successfully!
Step 1.10:保存并使用 join 命令让 worker 加入集群
在Your Kubernetes control-plane has initialized successfully! 下面有一段代码,长这样
^ 把token 和discovery-token-ca-cert-hash改成对应的内容
成功会显示 This node has joined the cluster
如果token过期了,在master上重新生成一条token
Step 1.12:安装 Weave Net 网络插件
在 master 上安装 Pod 网络插件,让集群真正具备节点间 Pod 网络能力。
Task 1 Summary
展开内容
1)创建了 3 台实验用 VM
k8s-master
k8s-worker1
k8s-worker2
2)给实验环境配好了 GCP 防火墙规则
你开放了这周 Kubernetes 实验需要的关键端口。
3)确认了 3 台机器的内网 IP 和主机名
你明确分清了:
External IP:你远程登录用
Internal IP:集群节点互通用
4)在三台机器上安装了 Docker
也就是底层容器运行环境。
5)在三台机器上安装了 cri-dockerd
也就是 Kubernetes 和 Docker 之间的适配层。
6)在三台机器上安装了 kubeadm / kubelet / kubectl
Kubernetes 的核心工具链。
7)在 master 上成功执行了 kubeadm init
把 k8s-master 初始化成控制平面。
8)让两个 worker 成功 join 到集群
9)安装了 Weave Net 网络插件
把 Pod 网络铺起来了。实验文档要求用 Weave Net。
10)最终 3 台节点全部 Ready
Task 2:Cluster 和 Node 管理
概括
kubectl cluster-info
kubectl get nodes
kubectl describe node <node-name>
如果只有 1 个节点,给 master 去掉 NoSchedule taint,让它也能跑 Pod
使用 cordon
使用 drain
使用 uncordon
Step 2.1 查看集群基本信息
你的 cluster 控制面地址是什么
当前有哪些核心服务已经暴露出来
集群里有哪些节点
它们的状态
角色
Kubernetes 版本
Step 2.2:查看某个节点的详细信息
Roles确认它是不是 worker / control-plane
Taints看节点有没有特殊“不要调度”的标记
Conditions看
Ready
MemoryPressure
DiskPressure
PIDPressure
Allocated resources看资源使用和分配概况
Kubernetes 出问题时,kubectl describe node 是非常常用的排查工具
Step 2.3:理解 master 为什么能运行,但 role 和 taint 是什么关系
如果环境里只有一个节点,有时需要把 master 变成可调度。
你现在是三节点环境,所以暂时不用执行 taint 移除命令。
taint = 给节点贴一个“别随便往我这里调度 Pod”的标签。
因为控制平面更偏向“管理者”,不一定希望普通业务 Pod 跑上来。
Step 2.4 解读describe node
Roles: <none>
这台机器没有被标成 control-plane,它就是普通 worker。
所以 worker1 显示 <none> 不是报错,而是正常现象
Taints: <none>
这台机器没有贴“不要往我这里调度 Pod”的特殊标签。
它现在是开放状态,允许 Kubernetes 把 Pod 调度到它上面。
后面你创建 Deployment 的时候,Pod 才能正常跑到 worker 节点上。
Unschedulable: false
没有 taint
也没有被 cordon
所以可以正常接收新 Pod
MemoryPressure: False
内存够用,没有紧张到 Kubernetes 需要警惕。
DiskPressure: False
PIDPressure: False
Ready: True
这台节点是健康的,并且已经准备好运行 Kubernetes 工作负载。
InternalIP: 10.19x.x.x
这台 k8s-worker1 的体检报告翻成人话就是:
Step 2.5:理解 cordon / drain / uncordon
cordon
drain
uncordon
文字结构图
Step 2.6 cordon
这表示:这台机器还是健康的,但先不让它接新 Pod
Step 2.7 uncordon
把刚才 cordon 的 worker1 恢复成可调度
k8s-worker1 会恢复成普通 Ready,不再显示调度禁用。
Step 2.8 drain
把 k8s-worker1 上的 Pod 尽量迁走,并让它不可调度
Task 3:创建和管理 Deployment
概括
查看 namespace 列表
创建一个你名字的 namespace
用 kubectl get ... -o wide 查看资源
直接用命令创建一个 nginx deployment
写 my-deployment.yml
kubectl apply -f my-deployment.yml
检查 Pod 是否创建成功
查看 logs
进入 Pod shell
scale 到 4 个副本
删除 deployment
Step 3.1:创建自己的 namespace
为什么要用namespace?
namespace = Kubernetes 里的资源隔间 / 文件夹
后面的 deployment、pod、service 不会和别人的混在一起
Step 3.2:查看 namespace 里的资源
目前没有deployment所以不会显示,和kubectl get nodes信息一样
Step 3.3:创建第一个 nginx deployment
在 jimmy 这个 namespace 里,创建一个 nginx deployment
Deployment 已经自动创建出了 Pod
Pod 已经进入 Running
没有重启过
它被调度到了 k8s-worker1
Step 3.4:用 YAML 创建第二个 deployment
Step 3.4.1:先创建 YAML 文件
Step 3.4.2:为什么这里要改成 YAML
因为 Kubernetes 更推荐“声明式管理”。
刚才:kubectl create deployment ...
更像临时直接下命令
现在:kind: Deployment
replicas: 2
image: nginx:latest
是把“我想要的应用配置”写成文件保存下来
Step 3.4.3:应用这个 YAML
Step 3.4.4:创建后检查
my-nginx-server这是前一步用命令创建的第一个 deployment
nginx-deployment这是刚才用 YAML 创建的第二个 deployment
my-nginx-server-...
nginx-deployment-...-2hf88
nginx-deployment-...-gzhfr
第一个 Deployment:my-nginx-server
第二个 Deployment:nginx-deployment
它的 YAML 里写了:replicas: 2,所以它会创建 2 个 Pod
它没有把所有 Pod 都堆在同一台机器上,而是把副本分布到了不同 worker 上
这些是 Pod IP = Pod 在 Kubernetes 网络里的地址
Step 3.5:查看 Pod 日志
Step 3.6:进入 Pod shell
Step 3.7:scale 到 4 个副本
然后 Kubernetes 自动帮你补出新增的 Pod
Kubernetes 的核心思想不是“你亲手创建了哪些 Pod”,
Task 4:Service
概括
先用 curl <pod-ip>:80 测试 nginx
或者用 kubectl port-forward
写 my-service.yml
kubectl apply -f my-service.yml
kubectl get svc -o wide
curl <service-ip>:8080
打开安全组里的 30003
浏览器访问 http://<public-ip>:30003
回答“是否做了负载均衡,如何验证”这个问题
Step 4.1:先验证 Pod 本身的 nginx 是否能访问
获得信息:不同Pod有不同IP,但它们都在提供相同的 nginx 页面
目前是跳过Service,直接访问某个具体Pod的地址
Step 4.2:创建 my-service.yml
创建一个 NodePort 类型 的 Service,把那 4 个 app=nginx 的 Pod 暴露出来
前面用YAML创建时,Pod标签就是app: nginx
在k8s-master上创建YAML文件
应用这个YAML
检查Service是否创建成功
检查这个Service背后有没有真正选中Pod
这三个端口是什么意思
Step 4.3:Service内部验证
先验证 Service 自己的 ClusterIP 能不能访问
Step 4.4 外部访问验证
使用master或者两个worker的 External IP访问
Step 4.6:回答实验文档的问题——有没有在背后做负载均衡?
答案:有
因为你的 my-service 不是指向某一个固定 Pod,
所以 Service 会把进入它的流量,转发到这 4 个 Pod 之一。
怎么验证
Task 4 Summary
先确认 Pod 本身能工作,再给这批 Pod 做一个统一入口 Service,然后从集群内和集群外去访问它
Task 5:Persistent Volume(Homework)
概括
安装 local path provisioner
检查它的 Pod 是否运行
写 pvc.yaml
创建 PVC
观察它为什么会 Pending
kubectl describe pvc ...
写 nginx-pod.yaml
创建 Pod 并挂载 PVC
kubectl port-forward
curl localhost:8080
注意 Pod 和 PVC 必须在同一个 namespace
任务细分
前面学的是“怎么把应用跑起来”,现在学的是“怎么让数据不要跟着 Pod 一起消失”。
PVC:我要一块存储的申请单
PV:真正分给你的存储空间
Pod 挂载 PVC:让应用用上这块存储
Pod 重建、删除后,容器里的数据本来很容易一起丢掉。
Step 5.1:安装 local path provisioner
Step 5.2:确认 provisioner 是否安装成功
Step 5.3:创建 PVC
Step 5.4:应用 PVC
Step 5.5:观察 PVC 为什么会是 Pending
现在只是提出了“我要一块 2Gi 的存储”,但这块本地卷通常要等到
真的有 Pod 来使用它,系统才更明确地去绑定和分配
Step 5.6:创建使用 PVC 的 Pod
Step 5.7:创建这个 Pod
nginx-volume-test 最终应该进入 Running
2)PVC 是否从 Pending 变成 Bound
Step 5.8:验证这个 Pod 是否能访问
curl localhost:8080 不是最强验证
更靠谱的验证方法——进入 Pod 看 /data
直接进 nginx-volume-test 这个 Pod,检查 /data 目录,并在里面写一个文件
PVC 挂载点 /data 真实存在
Pod 能向这块卷写入数据
Pod 也能从这块卷读出数据
Summary
怎么让 Kubernetes 里的应用,不把数据和 Pod 一起丢掉。
总结Lab 4
从 0 搭了一个 Kubernetes 集群,在上面部署了 nginx,验证了扩缩容和 Service 暴露,还实现了持久化存储。
也就是说,这次实验已经不只是“会跑容器”,而是开始真正接触:
master = 管理者
worker = 干活者
这是一台普通 worker 节点,
没有被禁止调度,
没有内存/磁盘/进程压力,
网络正常,
而且已经处于 Ready 状态,
所以它现在可以正常接收 Pod。
cordon
-> 不再接收新 Pod
drain
-> 清空现有 Pod + 不再接收新 Pod
uncordon
-> 恢复接收新 Pod
pod/nginx-deployment-96b9d695-2hf88
pod/nginx-deployment-96b9d695-gzhfr
外部访问 NodeIP:30003
↓
Service:8080
↓
Pod:80
nodePort 是外部入口
port 是 Service 端口
targetPort 是容器端口
kubectl get endpoints my-service -n jimmy
PVC = 你去仓库申请一个储物柜
PV = 仓库真正分给你的储物柜
Pod = 使用这个储物柜的人
cat > 1-install-docker.sh <<'EOF'
#!/bin/bash
# note: run this script as root user
# donwload public key for docker repository
apt update && apt dist-upgrade -y
apt install -y apt-transport-https ca-certificates curl gnupg software-properties-common lsb-release
mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# add docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
usermod -aG docker ubuntu
echo "Docker installation step has completed. Please logout and login again."
EOF
cat > 2-install-cri_adapter.sh <<'EOF'
#!/bin/bash
cd ~
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.16/cri-dockerd_0.3.16.3-0.ubuntu-jammy_amd64.deb
sudo dpkg -i cri-dockerd_0.3.16.3-0.ubuntu-jammy_amd64.deb
EOF
cat > 3-install_kubexxx.sh <<'EOF'
#!/bin/bash
apt update && apt install -y apt-transport-https ca-certificates curl
#Download the Google public signing key:
sudo curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
#turn off swap
swapoff -a
#Add K8s repository:
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt update && sudo apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
kubeadm version
echo "If you can see the output of version numbers, Kubernetes utilities have been installed successfully."
EOF
chmod +x 1-install-docker.sh 2-install-cri_adapter.sh 3-install_kubexxx.sh
ls -l
sudo bash ~/1-install-docker.sh
sudo bash ~/2-install-cri_adapter.sh
ls /var/run/cri-dockerd.sock
sudo bash ~/3-install_kubexxx.sh
kubeadm version
kubectl version --client
sudo kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--cri-socket=unix:///var/run/cri-dockerd.sock \
--apiserver-advertise-address=内网IP
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
sudo kubeadm join 10.192.x.x:6443 \
--token xxxxxxxxxxxxxxxxxxxx \
--discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxx \
--cri-socket=unix:///var/run/cri-dockerd.sock
kubeadm token create --print-join-command
kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml
kubectl get pods -n kube-system
Kubernetes Cluster
├── k8s-master (control-plane)
│ ├── kube-apiserver
│ ├── kube-controller-manager
│ ├── kube-scheduler
│ └── etcd
├── k8s-worker1 (worker)
└── k8s-worker2 (worker)
每台机器上都有:
├── Docker
├── cri-dockerd
└── kubelet / kubeadm / kubectl
kubectl describe node k8s-worker1
kubectl describe node k8s-master
kubectl describe node k8s-worker1 | grep -E "Roles:|InternalIP:|Taints:|Ready|MemoryPressure|DiskPressure|PIDPressure" -A2 -B2
kubectl cordon k8s-worker1
kubectl get nodes
kubectl drain k8s-worker1 --ignore-daemonsets --delete-emptydir-data
kubectl create namespace jimmy
kubectl get namespace
kubectl get nodes,deployments,pods -n jimmy -o wide
kubectl create deployment my-nginx-server --image=nginx -n jimmy
kubectl get nodes,deployments,pods -n jimmy -o wide
cat > my-deployment.yml<<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
EOF
kubectl apply -f my-deployment.yml -n jimmy
kubectl get deployments,pods -n jimmy -o wide
pod/my-nginx-server-65b758d9bd-dsgt5
kubectl logs nginx-deployment-96b9d695-2hf88 -n jimmy
kubectl exec -it nginx-deployment-96b9d695-2hf88 -n jimmy -- /bin/bash
kubectl scale deployment nginx-deployment --replicas=4 -n jimmy
kubectl get deployments,pods -n jimmy -o wide
Deployment (nginx-deployment)
↓
期望副本数 replicas: 4
↓
ReplicaSet 发现现在只有 2 个
↓
自动再创建 2 个 Pod
↓
最终达到 4 个
nginx-deployment-96b9d695-2hf88
IP: 10.32.0.3
cat > my-service.yml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 8080
targetPort: 80
nodePort: 30003
EOF
kubectl apply -f my-service.yml -n jimmy
kubectl get svc -n jimmy -o wide
kubectl get endpoints my-service -n jimmy
kubectl get svc -n jimmy -o wide
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml
kubectl -n local-path-storage get pods
cat > pvc.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi
EOF
kubectl apply -f pvc.yaml -n jimmy
kubectl describe pvc local-path-pvc -n jimmy
cat > nginx-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: nginx-volume-test
spec:
containers:
- name: nginx-volume-test
image: nginx:stable-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: task5-pv
mountPath: /data
ports:
- containerPort: 80
volumes:
- name: task5-pv
persistentVolumeClaim:
claimName: local-path-pvc
EOF
kubectl apply -f nginx-pod.yaml -n jimmy
kubectl get pod,pvc,pv -n jimmy
kubectl exec -it nginx-volume-test -n jimmy -- /bin/sh
ls -al /data
echo hello > /data/test.txt
cat /data/test.txt
exit