从Docker到Kubernetes:我的容器云部署与应用实战之旅

前言:为什么我们需要容器云?

在云计算和微服务架构大行其道的今天,“容器”早已不是一个陌生的词汇。它以其轻量级、可移植、高效能的特点,彻底改变了我们开发、交付和运行应用程序的方式。然而,当我们的应用从单体架构拆分成数十甚至上百个微服务时,一个新的挑战摆在了面前:如何高效地管理这些数量庞大的容器?如何确保它们的高可用、弹性伸缩和自动化运维?

答案就是——容器云(Container Cloud)

容器云并非简单的“容器+云”,它是一个以容器为核心,集成了编排、调度、网络、存储、监控和安全等一系列能力的综合性平台。它以Kubernetes(K8s)为事实标准,为我们提供了一个强大的操作系统,来管理和调度整个数据中心的资源。

本篇博文将基于我近期的学习和实践,系统地总结容器云的部署与应用。我将从核心概念入手,一步步带你搭建一个属于自己的Kubernetes集群,并部署一个完整的微服务应用,最后分享一些我在实践中遇到的问题和思考。希望能为正在学习或准备踏入容器云领域的你,提供一份有价值的参考。

第一部分:基石——深入理解容器与Kubernetes核心概念

在动手之前,我们必须夯实理论基础。知其然,更要知其所以然。

1. 容器技术的本质

容器的本质是一个被隔离的进程。它利用Linux内核提供的两大核心技术实现:

  • Namespaces(命名空间): 提供了进程级别的隔离。例如,PID Namespace让容器拥有独立的进程ID空间,Network Namespace让容器拥有独立的网络栈,Mount Namespace让容器拥有独立的文件系统视图。这就像给每个进程盖了一个独立的“单间”。
  • Cgroups(控制组): 负责资源的限制和审计。它可以精确地限制一个进程组(即容器)可以使用的CPU、内存、磁盘I/O等资源上限,防止某个容器耗尽宿主机资源,影响其他容器。这就像是给每个“单间”规定了水电用量上限。

Docker等容器引擎,正是在这两项技术之上,封装了镜像(Image)、仓库(Registry)等更易用的概念,极大地降低了容器的使用门槛。

2. Kubernetes:容器编排的王者

如果说Docker是制造集装箱的工厂,那么Kubernetes就是那个调度万千货轮的超级港口。它的核心设计哲学是声明式API控制器模式。你只需要告诉K8s“我想要什么状态”(例如:我需要3个Nginx副本),K8s内部的控制器就会持续工作,确保“实际状态”无限趋近于“期望状态”。

以下是K8s中最核心的几个概念,必须烂熟于心:

  • Pod: K8s中最小的调度和管理单元。一个Pod可以包含一个或多个紧密耦合的容器(通常是主业务容器+辅助Sidecar容器),它们共享网络和存储卷。Pod是短暂的,随时可能被销毁和重建。
  • Deployment: 用于管理无状态应用的控制器。它通过管理ReplicaSet来保证指定数量的Pod副本始终在运行,并支持滚动更新和回滚。这是我们部署Web应用最常用的方式。
  • Service: 为一组功能相同的Pod提供一个稳定的访问入口(ClusterIP)和负载均衡。由于Pod的IP是动态变化的,Service通过Label Selector找到后端Pod,解决了服务发现的问题。
  • Ingress: 集群外部流量进入集群内部的网关。它定义了HTTP/HTTPS路由规则,可以将不同域名的请求转发到集群内不同的Service上,通常需要一个Ingress Controller(如Nginx Ingress Controller)来实现。
  • ConfigMap & Secret: 用于将配置信息与容器镜像解耦。ConfigMap存储非敏感的配置数据(如配置文件、环境变量),而Secret则专门用于存储密码、Token、密钥等敏感信息。
  • PersistentVolume (PV) & PersistentVolumeClaim (PVC): K8s的持久化存储抽象层。管理员预先创建好PV(代表实际的存储资源,如NFS、云盘),开发者通过PVC来申请所需大小和访问模式的存储。这种解耦使得应用无需关心底层存储的具体实现。
第二部分:实战——从零搭建高可用Kubernetes集群

理论准备就绪,现在让我们开始动手。本次实战的目标是搭建一个包含1个Master节点和2个Worker节点的K8s集群。

1. 环境准备
角色 IP地址 操作系统 配置
k8s-master 192.168.1.10 Ubuntu 22.04 LTS 2C4G
k8s-node1 192.168.1.11 Ubuntu 22.04 LTS 2C4G
k8s-node2 192.168.1.12 Ubuntu 22.04 LTS 2C4G

所有节点都需要执行的初始化操作:

  1. 关闭防火墙和Swap:
    sudo ufw disable
    sudo swapoff -a
    # 永久关闭swap,注释掉/etc/fstab中的swap行
    sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
    
  2. 设置主机名和Hosts解析:
    # 在master节点执行
    sudo hostnamectl set-hostname k8s-master
    # 在node1节点执行
    sudo hostnamectl set-hostname k8s-node1
    # ...以此类推
    
    # 在所有节点的/etc/hosts文件中添加
    192.168.1.10 k8s-master
    192.168.1.11 k8s-node1
    192.168.1.12 k8s-node2
    
  3. 加载必要的内核模块并设置内核参数:
    cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
    overlay
    br_netfilter
    EOF
    
    sudo modprobe overlay
    sudo modprobe br_netfilter
    
    cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-iptables  = 1
    net.bridge.bridge-nf-call-ip6tables = 1
    net.ipv4.ip_forward                 = 1
    EOF
    
    sudo sysctl --system
    
  4. 安装容器运行时(Containerd):
    sudo apt-get update
    sudo apt-get install -y containerd
    # 生成默认配置并修改SystemdCgroup为true
    sudo mkdir -p /etc/containerd
    containerd config default | sudo tee /etc/containerd/config.toml
    sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
    sudo systemctl restart containerd
    
  5. 安装Kubeadm, Kubelet, Kubectl:
    # 添加阿里云的K8s apt源
    curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/kubernetes-archive-keyring.gpg
    echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
    
    sudo apt-get update
    sudo apt-get install -y kubelet kubeadm kubectl
    sudo apt-mark hold kubelet kubeadm kubectl # 防止自动升级
    
2. 初始化Master节点

k8s-master节点上执行:

sudo kubeadm init \
  --apiserver-advertise-address=192.168.1.10 \
  --image-repository registry.aliyuncs.com/google_containers \
  --pod-network-cidr=10.244.0.0/16 # Flannel网络插件的默认网段

提示: --image-repository参数指定了国内镜像源,避免因无法访问Google镜像仓库而失败。--pod-network-cidr必须与你后续要安装的CNI网络插件的网段一致。

初始化成功后,会输出一段kubeadm join命令,请务必保存好这段命令,它是Worker节点加入集群的凭证。

然后,按照提示配置kubectl:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
3. 安装CNI网络插件

这里我们选择简单易用的Flannel。

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

等待片刻,直到kube-flannel-ds相关的Pod都处于Running状态。

4. 加入Worker节点

k8s-node1k8s-node2上,分别执行之前保存的kubeadm join命令。

5. 验证集群状态

回到Master节点,执行:

kubectl get nodes

如果看到所有节点的状态都是Ready,恭喜你,一个基础的K8s集群已经搭建成功!

NAME         STATUS   ROLES           AGE     VERSION
k8s-master   Ready    control-plane   10m     v1.27.x
k8s-node1    Ready    <none>          5m      v1.27.x
k8s-node2    Ready    <none>          5m      v1.27.x
第三部分:应用——部署一个完整的微服务示例

为了演示K8s的应用能力,我们将部署一个经典的微服务组合:一个前端Vue应用、一个后端Spring Boot API和一个Redis缓存。

1. 准备YAML文件

我们将所有资源配置写在一个deployment.yaml文件中,便于管理。

# --- Redis Deployment & Service ---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  selector:
    app: redis
  ports:
  - port: 6379
    targetPort: 6379

# --- Backend API Deployment & Service ---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend-api
  template:
    metadata:
      labels:
        app: backend-api
    spec:
      containers:
      - name: api
        image: your-registry/backend-api:v1.0 # 替换为你的镜像地址
        env:
        - name: REDIS_HOST
          value: "redis-service" # 使用Service名称作为主机名
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend-api
  ports:
  - port: 80
    targetPort: 8080

# --- Frontend Deployment & Service ---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: web
        image: your-registry/frontend:v1.0 # 替换为你的镜像地址
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80

# --- Ingress ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx # 假设已安装Nginx Ingress Controller
  rules:
  - host: myapp.local # 在你的电脑上修改hosts文件指向Ingress Controller的IP
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
2. 部署与验证
  1. 部署应用:
    kubectl apply -f deployment.yaml
    
  2. 检查Pod和服务状态:
    kubectl get pods,svc,ingress
    
    确保所有Pod都处于Running状态,Service有对应的Endpoints,Ingress有正确的ADDRESS。
  3. 本地测试:
    在你的本地电脑上,修改/etc/hosts(Mac/Linux)或C:\Windows\System32\drivers\etc\hosts(Windows),添加一行:
    <你的Ingress_Controller_Node_IP> myapp.local
    
    然后在浏览器中访问 http://myapp.local,你应该能看到前端页面,并且它能正常调用后端API。
第四部分:进阶——生产环境下的关键考量

上面的部署只是一个起点。在生产环境中,我们还需要考虑更多问题。

  • 可观测性: 你必须知道集群里发生了什么。
    • 日志(Logging): 使用EFK(Elasticsearch, Fluentd, Kibana)或Loki栈来集中收集和分析容器日志。
    • 监控(Monitoring): 使用Prometheus + Grafana来监控集群节点、Pod的资源使用情况以及应用的业务指标。
    • 链路追踪(Tracing): 对于复杂的微服务调用,使用Jaeger或SkyWalking来追踪一次请求的完整路径,快速定位性能瓶颈。
  • 安全性:
    • RBAC(基于角色的访问控制): 严格遵循最小权限原则,为不同的用户和服务账号分配精确的权限。
    • 网络策略(NetworkPolicy): 定义Pod之间的网络访问规则,实现微隔离,防止攻击者在攻破一个Pod后横向移动。
    • 镜像安全: 定期扫描镜像漏洞,只使用来自可信源的镜像。
  • GitOps: 将集群的配置和应用部署都代码化,并存放在Git仓库中。使用ArgoCD或FluxCD等工具,实现配置的自动同步和版本化管理。这是目前最先进的K8s运维范式。
  • 服务网格(Service Mesh): 当服务间通信变得极其复杂时,可以考虑引入Istio或Linkerd等服务网格。它们将流量管理、安全、可观测性等能力从业务代码中剥离出来,作为一个独立的基础设施层来处理。
第五部分:踩坑记录与心得体会

在实践中,我遇到了不少问题,也收获了很多心得。

  • 坑1:Pod一直处于Pending状态。
    • 排查: kubectl describe pod <pod-name> 查看事件。
    • 原因: 通常是资源不足(CPU/内存)或没有匹配的节点(NodeSelector/Taint)。
    • 解决: 扩容节点或调整Pod的资源请求(requests)。
  • 坑2:Pod之间无法通信。
    • 排查: 检查Service的Selector是否与Pod的Label匹配;检查CNI插件是否正常工作(kubectl get pods -n kube-system)。
    • 原因: Label不匹配是最常见的原因。
    • 解决: 仔细核对YAML文件中的Label定义。
  • 坑3:Ingress无法访问。
    • 排查: 检查Ingress Controller是否已安装并正常运行;检查Ingress规则中的hostpath是否正确;检查本地hosts文件或DNS解析。
    • 原因: Ingress Controller未正确配置或未暴露端口。
    • 解决: 确保Ingress Controller的Service类型为LoadBalancer或通过NodePort暴露,并正确映射端口。

心得体会:

  1. YAML工程师的自我修养: K8s的世界是由YAML文件构成的。熟练掌握YAML语法和各种资源的字段含义,是成为一名合格K8s使用者的基本功。善用kubectl explain <resource>命令来查询字段说明。
  2. 拥抱声明式思维: 不要再想着“如何创建一个Pod”,而是去想“我需要一个什么样的Deployment”。这种思维的转变至关重要。
  3. 文档是最好的老师: K8s的官方文档非常详尽,遇到问题时,第一时间查阅官方文档往往比搜索引擎更有效。
  4. 社区的力量: K8s拥有一个庞大而活跃的社区。绝大多数问题都能在社区中找到答案。学会提问,善于利用GitHub Issues和Stack Overflow。
结语

从Docker的单机容器到Kubernetes的集群编排,我们走过的是一条追求更高效率、更强韧性和更优体验的技术演进之路。容器云不是终点,而是一个新的起点。它为我们构建云原生应用提供了坚实的基础。

希望通过这篇博文,能让你对容器云的部署与应用有一个更全面、更深入的认识。技术之路,道阻且长,行则将至。让我们一起在云原生的浪潮中,不断探索,持续学习!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐