Docker与Kubernetes
Docker容器与Kubernetes集群 -- 从零开始的实战指南
目录
第一部分:Docker容器技术
- 容器概念:为什么我们需要容器?
- Docker介绍与安装
- Docker客户端操作
- 镜像管理:打造你自己的"安装包"
- Docker仓库:镜像的"应用商店"
- 数据卷:让数据不再"随容器消亡"
- 网络配置:容器如何"上网冲浪"
- 网络架构:深入理解docker0网桥
- 跨主机容器通讯
- Namespace与Cgroup:隔离与限制的魔法
- Overlay文件系统:写时复制的秘密
第二部分:Kubernetes集群编排
- K8s介绍:容器世界的"交响乐指挥家"
- YAML基础:写给K8s的"说明书"
- Master与Node部署
- kubectl使用:与集群对话
- Dashboard:集群的Web管理界面
- Pod:K8s的最小调度单元
- ReplicaSet与Deployment:副本管理与滚动更新
- Service:让外部用户找到你的服务
- Volume:K8s中的数据持久化
- Job与CronJob:批处理与定时任务
- DaemonSet:每个节点的"管家"
- HPA:自动弹性伸缩
第一部分:Docker容器技术
1. 容器概念:为什么我们需要容器?
生活中的问题
想象你是一位机房管理员,你需要在机房的三台服务器上分别安装 Python、Jupyter Notebook、各种数据库……每台电脑的系统版本还不一样。每次都要重复折腾一遍,太痛苦了!
问题: 如何让"我的程序在你的电脑上也能跑起来"?
类比理解:
- 虚拟机 = 盖一栋独立房子: 每栋房子有自己的水电、地基,虽然隔离好,但造价高、启动慢、占地方大。
- 容器 = 标准化的集装箱: 不管里面装的是电视还是冰箱,集装箱外面的尺寸是统一的,轮船(服务器)只管搬运就行。
虚拟机 vs 容器
| 对比维度 | 虚拟机 (VM) | 容器 (Container) |
|---|---|---|
| 类比 | 独栋别墅 | 公寓单元房 |
| 启动速度 | 分钟级 | 秒级甚至毫秒级 |
| 占用内存 | GB级(需要完整OS) | MB级(共享宿主机内核) |
| 隔离程度 | 强(独立内核) | 中(共享内核) |
| 单台服务器运行数量 | 几十个 | 数百到上千个 |
| 性能损耗 | 较大(Hypervisor层) | 极小(接近裸机) |
容器的核心技术
容器并不是什么全新的黑科技,它依赖Linux内核的三个核心能力:
- Namespace(命名空间) —— 实现"隔离":让每个容器以为自己独享整台机器
- Cgroup(控制组) —— 实现"限制":限制每个容器能用多少CPU和内存
- UnionFS(联合文件系统) —— 实现"分层":多个只读层叠加,写操作在最上层
容器的使用场景
- 开发、测试、部署一体化——"在我机器上能跑"不再是借口
- 创建隔离的运行环境,互不干扰
- 快速搭建教学环境(一键启动Python/Jupyter/数据库)
- 大规模服务器部署,一台机器跑几百个服务
思考题
如果你要在学校机房为30位同学搭建独立的Python编程环境,用虚拟机还是容器更合适?为什么?
2. Docker介绍与安装
Docker是什么?
Docker 是一个开源的容器引擎,可以把应用程序和它所需的所有依赖打包成一个标准化的单元(容器),然后"一次打包,到处运行"。
核心组件:
- Docker Client(客户端) —— 你用来发命令的工具
- Docker Daemon(守护进程) —— 在后台真正干活的"管家"
- Docker Image(镜像) —— 容器的"蓝图"或"菜谱"
- Docker Container(容器) —— 镜像运行起来的"实例"
- Docker Registry(仓库) —— 存放镜像的"应用商店"
在CentOS 7上安装Docker
# 第一步:安装系统工具
yum install -y yum-utils device-mapper-persistent-data lvm2
# 第二步:添加阿里云Docker源(国内更快)
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 第三步:安装Docker CE(社区版,免费)
yum makecache fast
yum -y install docker-ce
# 第四步:启动并设置开机自启
systemctl start docker
systemctl enable docker
验证安装成功
[root@docker ~]# docker info
Client:
Version: 20.10.x
...
Server:
Containers: 0
Images: 0
Storage Driver: overlay2
...
[root@docker ~]# docker version
Client: Docker Engine - Community
Version: 20.10.x
Server: Docker Engine - Community
Version: 20.10.x
Docker服务管理常用命令
systemctl start docker # 启动Docker
systemctl stop docker # 停止Docker
systemctl restart docker # 重启Docker
systemctl status docker # 查看Docker状态
systemctl enable docker # 开机自启
配置国内镜像加速(解决下载慢的问题)
# 创建配置文件
cat > /etc/docker/daemon.json <<EOF
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://docker.mirrors.ustc.edu.cn"
]
}
EOF
# 重启Docker使配置生效
systemctl daemon-reload
systemctl restart docker
常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
Cannot connect to Docker daemon | Docker服务未启动 | systemctl start docker |
permission denied | 当前用户无权限 | 用sudo或加入docker组 |
| 拉取镜像超时 | 网络问题 | 配置镜像加速器 |
3. Docker客户端操作
核心命令速查表
| 命令 | 功能 | 类比 |
|---|---|---|
docker pull | 下载镜像 | 从应用商店下载App |
docker images | 查看本地镜像 | 查看手机已安装的App |
docker run | 从镜像创建并运行容器 | 打开App |
docker ps | 查看运行中的容器 | 查看正在运行的App |
docker exec | 进入容器内部执行命令 | 进入App的设置界面 |
docker logs | 查看容器日志 | 查看App的崩溃日志 |
docker stop/start/rm | 停止/启动/删除容器 | 关闭/重启/卸载App |
docker rmi | 删除镜像 | 卸载App安装包 |
拉取镜像
# 从Docker Hub拉取nginx镜像
[root@docker ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
...
Digest: sha256:xxxxx
Status: Downloaded newer image for nginx:latest
# 拉取指定版本的镜像
[root@docker ~]# docker pull mysql:5.7
# 查看本地所有镜像
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e29e40 2 weeks ago 142MB
mysql 5.7 1b12f2e9257b 3 weeks ago 462MB
运行容器
# 最基本的运行方式
[root@docker ~]# docker run --name web1 -d nginx
bd054d7986f809cba9bbfecb7a14dc5dbf16e3643aa265dd3a5b136770260333
# 查看运行中的容器
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
bd054d7986f8 nginx "nginx -g 'daemon of…" Up 3 seconds 80/tcp, 443/tcp web1
docker run常用参数:
-d # 后台运行(detached模式)
--name # 给容器起名字
-it # 交互式运行(分配终端)
-p 8080:80 # 端口映射(宿主机8080 → 容器80)
-P # 随机端口映射
-v # 挂载数据卷
-e # 设置环境变量
--restart=always # Docker重启后自动启动容器
端口映射
# -P 随机端口映射
[root@docker ~]# docker run -d -P --name web2 nginx
[root@docker ~]# docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
006d1043652b nginx Up 15s 0.0.0.0:32769->80/tcp, 0.0.0.0:32768->443/tcp web2
# -p 指定端口映射(宿主机端口:容器端口)
[root@docker ~]# docker run -d -p 80:80 --name web3 nginx
# 绑定到指定IP
[root@docker ~]# docker run -d -p 192.168.100.100:80:80 --name web4 nginx
# 多端口映射
[root@docker ~]# docker run -d -p 80:80 -p 443:443 --name web5 nginx
进入容器内部
# 在容器中执行单条命令
[root@docker ~]# docker exec web1 ls /usr/share/nginx/html
50x.html
index.html
# 交互式登录容器(像SSH登录一样)
[root@docker ~]# docker exec -it web1 /bin/bash
root@bd054d7986f8:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
root@bd054d7986f8:/# exit
查看容器日志
# 查看容器日志
[root@docker ~]# docker logs web1
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, ...
172.17.0.1 - - [10/Jun/2024:08:30:00 +0000] "GET / HTTP/1.1" 200 615
# 实时跟踪日志(类似tail -f)
[root@docker ~]# docker logs -f web1
容器的生命周期管理
# 停止容器
[root@docker ~]# docker stop web1
# 启动已停止的容器
[root@docker ~]# docker start web1
# 重启容器
[root@docker ~]# docker restart web1
# 暂停/恢复容器
[root@docker ~]# docker pause web1
[root@docker ~]# docker unpause web1
# 强制删除运行中的容器
[root@docker ~]# docker rm -f web1
# 批量停止并删除所有容器
[root@docker ~]# docker stop $(docker ps -q)
[root@docker ~]# docker rm $(docker ps -aq)
查看容器详细信息
# 查看容器的元数据(IP地址、挂载点、环境变量等)
[root@docker ~]# docker inspect web1
[
{
"Id": "bd054d7986f8...",
"State": {
"Status": "running",
"Pid": 12345,
...
},
"NetworkSettings": {
"IPAddress": "172.17.0.2",
...
}
}
]
实战:快速启动一个MySQL数据库
[root@docker ~]# docker run --name mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=robin123 \
-d mysql:5.7
# 等待几秒后测试连接
[root@docker ~]# mysql -h 127.0.0.1 -uroot -probin123
mysql> show databases;
思考题
为什么
docker run -d nginx之后容器会立即退出?提示:容器需要什么才能保持运行?
4. 镜像管理:打造你自己的"安装包"
镜像是什么?
类比: 镜像就像一份详细的菜谱,记录了操作系统、软件、配置的所有信息。容器就是用这份菜谱"做出来的菜"。同一份菜谱可以做出很多份一样的菜(容器)。
方法一:docker commit(基于容器创建镜像)
适合快速记录你对容器做的修改。
# 第一步:运行一个基础容器并进入
[root@docker ~]# docker run -it --name web centos:7 /bin/bash
# 在容器内安装nginx
[root@web /]# yum install -y epel-release && yum install -y nginx
[root@web /]# echo 'Welcome to my server!' > /usr/share/nginx/html/index.html
[root@web /]# exit
# 第二步:将修改提交为新镜像
[root@docker ~]# docker commit web my-nginx:v1
sha256:4ba57203004b...
# 第三步:用新镜像运行容器
[root@docker ~]# docker run -d -p 80:80 --name web_server my-nginx:v1 \
nginx -g "daemon off;"
方法二:Dockerfile(推荐,可复现)
Dockerfile是一个文本文件,记录了构建镜像的每一步操作。
# 创建构建目录
[root@docker ~]# mkdir -p /root/my-nginx && cd /root/my-nginx
# 编写Dockerfile
[root@docker my-nginx]# cat Dockerfile
# 基础镜像(必须放在第一行)
FROM centos:7
# 作者信息
MAINTAINER teacher "teacher@school.edu.cn"
# 安装软件(合并RUN减少层数)
RUN yum install -y epel-release && \
yum install -y nginx && \
yum clean all && \
rm -rf /var/cache/yum/*
# 复制自定义首页
COPY index.html /usr/share/nginx/html/index.html
# 声明端口
EXPOSE 80
# 容器启动时运行的命令
CMD ["nginx", "-g", "daemon off;"]
# 构建镜像(注意末尾的"."表示当前目录)
[root@docker my-nginx]# docker build -t my-nginx:v2 .
Sending build context to Docker daemon 3.072kB
Step 1/6 : FROM centos:7
---> eeb6ee3f44bd
Step 2/6 : MAINTAINER teacher "teacher@school.edu.cn"
---> Running in abc123
Step 3/6 : RUN yum install -y epel-release && ...
---> Running in def456
---> ghi789
...
Successfully built xxxxx
Successfully tagged my-nginx:v2
# 运行新镜像
[root@docker my-nginx]# docker run -d -p 80:80 --name web_test my-nginx:v2
Dockerfile常用指令速查
| 指令 | 作用 | 示例 |
|---|---|---|
FROM | 指定基础镜像 | FROM centos:7 |
RUN | 构建时执行命令 | RUN yum install -y nginx |
COPY | 复制本地文件到镜像 | COPY index.html /usr/share/nginx/html/ |
ADD | 类似COPY,还能解压tar | ADD app.tar.gz /opt/ |
CMD | 容器启动时执行的命令 | CMD ["nginx", "-g", "daemon off;"] |
ENTRYPOINT | 容器启动时执行的入口命令 | ENTRYPOINT ["/usr/sbin/nginx"] |
EXPOSE | 声明容器端口 | EXPOSE 80 |
ENV | 设置环境变量 | ENV APP_HOME /opt/app |
WORKDIR | 设置工作目录 | WORKDIR /app |
VOLUME | 声明数据卷挂载点 | VOLUME ["/data"] |
USER | 指定运行用户 | USER nginx |
CMD vs ENTRYPOINT的区别
# CMD:可以被docker run后面的命令覆盖
CMD ["nginx", "-g", "daemon off;"]
# docker run my-image /bin/bash ← 会覆盖CMD
# ENTRYPOINT:不会被覆盖,docker run后面的参数会追加
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# docker run my-image -t ← 变成 nginx -t
镜像的导入与导出
# 导出镜像为文件(方便离线传输)
[root@docker ~]# docker save my-nginx:v2 > /tmp/my-nginx.tar
[root@docker ~]# du -sh /tmp/my-nginx.tar
385M /tmp/my-nginx.tar
# 在其他机器上导入
[root@docker2 ~]# docker load < /tmp/my-nginx.tar
# 导出容器(注意:会丢失历史记录)
[root@docker ~]# docker export web1 > /tmp/web1-export.tar
[root@docker2 ~]# docker import /tmp/web1-export.tar my-image:v3
镜像优化技巧
- 减少镜像层数: 把多个RUN合并成一行
- 清理缓存:
yum clean all && rm -rf /var/cache/yum/* - 使用.dockerignore: 排除不需要的文件
- 多阶段构建: 编译阶段和运行阶段分离
# 多阶段构建示例
# 第一阶段:编译
FROM golang:1.16 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 第二阶段:运行(只复制编译结果)
FROM alpine:latest
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]
5. Docker仓库:镜像的"应用商店"
Docker Hub(公有仓库)
类比: Docker Hub 就像手机上的"应用商店",你可以搜索、下载别人做好的镜像,也可以上传自己的。
# 登录Docker Hub
[root@docker ~]# docker login
Username: your_username
Password: ********
Login Succeeded
# 给镜像打标签(格式:用户名/镜像名)
[root@docker ~]# docker tag my-nginx:v2 robinround/my-nginx:v2
# 推送到Docker Hub
[root@docker ~]# docker push robinround/my-nginx:v2
# 在其他机器上拉取
[root@docker2 ~]# docker pull robinround/my-nginx:v2
搭建私有Registry
企业内部使用,不依赖外网,更安全更快。
# 服务端(192.168.100.103)
[root@registry ~]# docker pull registry
[root@registry ~]# docker run --name registry_server -d \
-p 5000:5000 \
-v /registry:/var/lib/registry \
registry
# 客户端配置(允许连接非HTTPS仓库)
[root@client ~]# vim /etc/docker/daemon.json
{
"insecure-registries": ["192.168.100.103:5000"]
}
[root@client ~]# systemctl restart docker
# 打标签并推送
[root@client ~]# docker tag my-nginx:v2 192.168.100.103:5000/my-nginx:v2
[root@client ~]# docker push 192.168.100.103:5000/my-nginx:v2
# 查询私有仓库中的镜像
[root@client ~]# curl -XGET http://192.168.100.103:5000/v2/_catalog
{"repositories":["my-nginx"]}
# 查询标签
[root@client ~]# curl -XGET http://192.168.100.103:5000/v2/my-nginx/tags/list
{"name":"my-nginx","tags":["v2"]}
Harbor(企业级私有仓库)
Harbor 是 VMware 开源的企业级 Docker 镜像仓库,相比 Registry 有以下优势:
- Web管理界面: 可视化搜索、管理镜像
- 权限控制: 基于角色的访问控制(RBAC)
- 镜像复制: 支持跨数据中心同步
- 安全扫描: 自动检测镜像漏洞
# 安装docker-compose(Harbor依赖)
[root@master ~]# yum install -y docker-compose
# 下载Harbor安装包
[root@master ~]# tar -xvf harbor-online-installer-v2.0.0.tgz
[root@master ~]# cd harbor/
# 配置Harbor
[root@master harbor]# cp harbor.yml.tmpl harbor.yml
[root@master harbor]# vim harbor.yml
# 修改hostname、密码等
# 执行安装
[root@master harbor]# ./install.sh
...
----Harbor has been installed and started successfully.----
# 访问:https://<你的IP>
# 默认账号:admin 默认密码:Harbor12345
6. 数据卷:让数据不再"随容器消亡"
问题的产生
问题: 容器删除后,里面的数据全没了!数据库的数据、用户上传的文件全丢了,怎么办?
类比: 容器就像一个临时工位,你一走东西就清了。数据卷就像你的个人储物柜,不管你换到哪个工位,柜子里的东西都在。
数据卷的特点
- 独立于容器生命周期 —— 容器删了,数据卷还在
- 存在于宿主机文件系统 —— 可以直接在宿主机上查看和修改
- 多个容器可共享 —— 实现容器间的数据共享
- 不影响镜像更新 —— 数据和镜像分离
绑定挂载(Bind Mount)
将宿主机的目录映射到容器内部:
# 准备宿主机目录
[root@docker ~]# mkdir -p /web/html
[root@docker ~]# echo "Hello from Host!" > /web/html/index.html
# 运行容器并挂载目录
[root@docker ~]# docker run --name web1 -d -p 80:80 \
-v /web/html:/usr/share/nginx/html nginx
# 验证:在容器内修改,宿主机同步
[root@docker ~]# docker exec web1 cat /usr/share/nginx/html/index.html
Hello from Host!
# 在宿主机修改,容器内同步
[root@docker ~]# echo "Updated!" > /web/html/index.html
[root@docker ~]# docker exec web1 cat /usr/share/nginx/html/index.html
Updated!
挂载配置文件
# 自定义nginx配置文件
[root@docker ~]# cat /mnt/nginx.conf
server {
listen 80;
server_name example.com;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
# 挂载单个文件
[root@docker ~]# docker run --name web2 -d -p 80:80 \
-v /mnt/nginx.conf:/etc/nginx/conf.d/default.conf \
-v /web/html:/usr/share/nginx/html nginx
# 修改配置文件后需要重启容器
[root@docker ~]# docker restart web2
Docker管理的卷(Named Volume)
# 创建命名卷
[root@docker ~]# docker volume create mydata
mydata
# 查看所有卷
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local mydata
# 使用命名卷
[root@docker ~]# docker run -d --name db \
-v mydata:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
# 查看卷的实际位置
[root@docker ~]# docker volume inspect mydata
[{
"Mountpoint": "/var/lib/docker/volumes/mydata/_data",
...
}]
数据卷容器(容器间共享数据)
# 创建数据卷容器
[root@docker ~]# docker run -it -d -v /dbdata --name dbdata centos:7
# 其他容器使用这个数据卷
[root@docker ~]# docker run -it -d --volumes-from dbdata --name app1 centos:7
[root@docker ~]# docker run -it -d --volumes-from dbdata --name app2 centos:7
# 在app1中写入数据
[root@docker ~]# docker exec app1 bash -c "echo 'shared data' > /dbdata/test.txt"
# 在app2中可以读到
[root@docker ~]# docker exec app2 cat /dbdata/test.txt
shared data
7. 网络配置:容器如何"上网冲浪"
问题
问题: 容器内部运行了一个Web服务监听80端口,但外面的人怎么访问到它?
四种网络模式
| 模式 | 参数 | 说明 | 类比 |
|---|---|---|---|
| bridge | --net=bridge(默认) | 独立网络,通过docker0网桥通信 | 公寓有自己的门牌号 |
| host | --net=host | 直接使用宿主机网络 | 合租,共用一个门牌号 |
| none | --net=none | 无网络,需手动配置 | 没有网线,完全断网 |
| container | --net=container:NAME | 共享指定容器的网络 | 和室友共用网络 |
bridge模式(默认)
# 默认就是bridge模式
[root@docker ~]# docker run -d --name web1 -p 80:80 nginx
# 查看容器IP
[root@docker ~]# docker inspect web1 | grep IPAddress
"IPAddress": "172.17.0.2",
host模式
# host模式直接使用宿主机IP和端口
[root@docker ~]# docker run -d --net=host --name web2 nginx
# 不需要端口映射,直接通过宿主机IP:80访问
[root@docker ~]# curl http://localhost:80
<!DOCTYPE html>
<html>
<head><title>Welcome to nginx!</title></head>
自定义网络
# 创建自定义网络(支持指定IP和容器间DNS解析)
[root@docker ~]# docker network create --subnet=172.18.0.0/16 mynetwork
# 查看网络列表
[root@docker ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
abc123 bridge bridge local
def456 mynetwork bridge local
# 在自定义网络中运行容器并指定IP
[root@docker ~]# docker run -d --name web3 \
--net mynetwork --ip 172.18.0.10 nginx
# 自定义网络内的容器可以通过容器名互相访问(内置DNS)
[root@docker ~]# docker exec web3 ping web4
容器互联(--link)
# 启动MySQL容器
[root@docker ~]# docker run -d --name mysqldb \
-e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
# 启动Nginx容器并链接到MySQL
[root@docker ~]# docker run -d -p 80:80 --name web \
--link mysqldb:mysql nginx
# 在web容器中可以直接通过"mysql"主机名访问
[root@docker ~]# docker exec -it web /bin/bash
root@web:/# cat /etc/hosts
172.17.0.2 mysql 5b59dcbdd017 mysqldb
root@web:/# env | grep MYSQL
MYSQL_PORT_3306_TCP_ADDR=172.17.0.2
MYSQL_PORT_3306_TCP_PORT=3306
注意:
--link是旧方式,推荐使用自定义网络(支持DNS解析,更灵活)。
8. 网络架构:深入理解docker0网桥
docker0是什么?
当你安装Docker后,系统会自动创建一个名为docker0的虚拟网桥,相当于一台"虚拟交换机"。所有容器都连接到这台交换机上。
[root@docker ~]# ip addr show docker0
8: docker0: <BROADCAST,MULTICAST,UP> mtu 1500
link/ether 02:42:ac:11:00:00 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global docker0
默认地址分配:
- docker0 IP:
172.17.0.1 - 子网掩码:
255.255.0.0(即 /16) - 可用地址:65534个(172.17.0.2 ~ 172.17.255.254)
容器网络的创建过程
- Docker创建一对虚拟网卡(veth pair),就像一根网线的两端
- 一端放入容器内,命名为
eth0 - 另一端接入docker0网桥,命名为
vethXXX - 从docker0子网中分配IP给容器
- 设置docker0的IP(172.17.0.1)为容器的默认网关
端口映射的原理
当使用-p 80:80时,Docker会自动添加iptables规则:
[root@docker ~]# iptables -t nat -L -n | grep 80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
这条规则的意思是:所有访问宿主机80端口的流量,都会被转发到容器172.17.0.2的80端口。
CNM(容器网络模型)
Docker的网络架构基于CNM(Container Network Model),核心概念:
- Sandbox(沙箱) —— 容器的网络栈(IP、路由、防火墙规则等)
- Endpoint(端点) —— 连接沙箱和网络的虚拟网卡
- Network(网络) —— 一组可以互相通信的端点
9. 跨主机容器通讯
问题
问题: 两台物理机上的容器,默认不能互相通信(因为各自的docker0分配了相同的IP段),怎么办?
方案一:直接路由
原理: 给两台主机的docker0分配不同子网,然后添加路由规则。
# 主机1:配置docker0使用172.17.0.0/24
[root@host1 ~]# cat /etc/docker/daemon.json
{"bip":"172.17.0.254/24"}
# 主机2:配置docker0使用172.17.1.0/24
[root@host2 ~]# cat /etc/docker/daemon.json
{"bip":"172.17.1.254/24"}
# 两台主机都重启Docker
systemctl restart docker
# 主机1添加路由(到对方网段,走对方物理IP)
[root@host1 ~]# route add -net 172.17.1.0 netmask 255.255.255.0 gw 192.168.0.205
# 主机2添加路由
[root@host2 ~]# route add -net 172.17.0.0 netmask 255.255.255.0 gw 192.168.0.204
# 清理iptables NAT规则(避免SNAT干扰)
[root@host1 ~]# iptables -t nat -F POSTROUTING
[root@host1 ~]# iptables -t nat -A POSTROUTING \
-s 172.17.0.0/24 ! -d 172.17.0.0/16 -j MASQUERADE
# 测试:在主机1的容器中ping主机2的容器
[root@host1 ~]# docker run -it --name test1 centos:7 /bin/bash
[root@test1 /]# ping 172.17.1.2
方案二:Flannel覆盖网络
Flannel 会自动为每台主机分配不重叠的子网,并通过VXLAN等技术实现跨节点通信。Kubernetes默认使用类似方案。
# 安装etcd(Flannel的配置存储)
yum install -y etcd
# 安装Flannel
yum install -y flannel
# 配置Flannel
[root@node1 ~]# cat /etc/sysconfig/flanneld
FLANNEL_ETCD_ENDPOINTS="http://192.168.0.204:2379,http://192.168.0.205:2379"
FLANNEL_ETCD_PREFIX="/atomic.io/network"
# 向etcd写入网络配置
etcdctl mk /atomic.io/network/config \
'{"Network":"10.10.0.0/16", "SubnetMin":"10.10.1.0", "SubnetMax":"10.10.254.0"}'
# 启动Flannel
systemctl start flanneld && systemctl enable flanneld
# 重启Docker(Docker会自动使用Flannel分配的网络)
systemctl daemon-reload
systemctl restart docker
# 验证:两台主机上的容器可以互相ping通
[root@test1 /]# ping 10.10.49.2
10. Namespace与Cgroup:隔离与限制的魔法
Namespace:实现"隔离"
类比: 就像考试时每位同学有独立的隔间,看不到别人的试卷,以为自己独享整个教室。
Docker利用Linux的6种Namespace实现容器的隔离:
| Namespace | 隔离什么 | 类比 |
|---|---|---|
| PID | 进程编号 | 每个人给自己的试卷编号都是"第1题" |
| NET | 网络(IP、端口、路由) | 每人有独立的网络接口 |
| MNT | 文件系统挂载点 | 每人有自己的文件柜 |
| UTS | 主机名和域名 | 每人可以给自己的隔间起名 |
| IPC | 进程间通信 | 每人有独立的信箱 |
| USER | 用户和组ID | 在自己的隔间里是"班长",在外面是普通同学 |
# 查看当前进程的Namespace
[root@docker ~]# ls -l /proc/$$/ns
lrwxrwxrwx 1 root root 0 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 uts -> uts:[4026531838]
# 在容器内看到的主机名是独立的(UTS Namespace)
[root@docker ~]# docker exec web1 hostname
bd054d7986f8
# 容器内的进程PID从1开始(PID Namespace)
[root@docker ~]# docker exec web1 ps aux
USER PID %CPU COMMAND
root 1 0.0 nginx: master process
nginx 7 0.0 nginx: worker process
Cgroup:实现"限制"
类比: 就像食堂给每个班级分配的饭菜配额——不管你多饿,最多只能拿这么多。
# 查看系统支持的cgroup子系统
[root@docker ~]# lssubsys
cpuset
cpu,cpuacct
memory
devices
freezer
net_cls
blkio
perf_event
hugetlb
主要子系统功能:
| 子系统 | 作用 |
|---|---|
| cpu | 控制CPU时间分配 |
| memory | 限制内存使用量 |
| blkio | 限制磁盘IO |
| cpuset | 绑定到特定CPU核心 |
| devices | 控制设备访问权限 |
实战:限制容器的CPU和内存
# 限制容器最多使用512MB内存和50% CPU
[root@docker ~]# docker run -d --name limited-app \
--memory=512m \
--cpus=0.5 \
nginx
# 验证资源限制
[root@docker ~]# docker inspect limited-app | grep -A5 "Memory"
"Memory": 536870912,
"NanoCpus": 500000000,
手动体验cgroup的威力
# 创建一个CPU控制组
[root@docker ~]# cd /sys/fs/cgroup/cpu/
[root@docker cpu]# mkdir test-group
# 设置CPU配额:每100000微秒中只用30000微秒(30%)
[root@docker cpu]# echo 30000 > test-group/cpu.cfs_quota_us
# 写一个死循环脚本
[root@docker ~]# cat > /tmp/cpu_test.sh << 'EOF'
#!/bin/bash
while true; do : ; done
EOF
chmod +x /tmp/cpu_test.sh
/tmp/cpu_test.sh &
# 此时top可以看到CPU占用100%
# 将进程加入控制组
[root@docker ~]# echo $! > /sys/fs/cgroup/cpu/test-group/tasks
# 再看top,CPU占用降到约30%
11. Overlay文件系统:写时复制的秘密
什么是OverlayFS?
类比: 想象你在用一张透明的薄膜覆盖在一张打印好的纸上。你可以在薄膜上写字、涂改,但原来的纸不会受影响。如果薄膜上某个位置和纸上同一位置都有内容,你看到的是薄膜上的(上层覆盖下层)。
核心特性
- 分层叠加: 下层(lowerdir)只读,上层(upperdir)可写
- 同名覆盖: 上下层同名文件,只显示上层的
- 写时复制(Copy-on-Write): 修改下层文件时,先复制到上层再修改
Docker镜像的分层结构
可写层(容器层) ← 容器运行时修改的文件写在这里
─────────────────
只读层 5 (nginx)
只读层 4 (epel-release)
只读层 3 (yum update)
只读层 2 (系统配置)
只读层 1 (CentOS base)
# 查看镜像的分层历史
[root@docker ~]# docker history nginx:latest
IMAGE CREATED SIZE COMMENT
abc123 2 days ago 0B nginx: daemon off
def456 2 days ago 101MB yum install nginx
ghi789 2 days ago 19MB yum install epel-release
jkl012 3 days ago 106MB yum update
OverlayFS的工作原理
Lowerdir(只读层) Upperdir(可写层)
├── index.html (原始) ├── newfile.txt (新建的文件)
├── config.conf └── config.conf (修改后的版本)
└── style.css
Merged(合并视图 = 容器看到的)
├── index.html ← 来自Lower(未修改)
├── config.conf ← 来自Upper(覆盖了下层)
├── style.css ← 来自Lower(未修改)
└── newfile.txt ← 来自Upper(新建)
删除操作的策略:
- 删除只在下层的文件 → 在上层创建一个"白名单"标记文件
- 删除只在上层的文件 → 直接从上层删除
- 修改只在下层的文件 → 先复制到上层,再修改(写时复制)
思考题
为什么10个基于同一个CentOS镜像的容器,只占用一份CentOS的磁盘空间?如果每个容器都修改了不同的文件呢?
第二部分:Kubernetes集群编排
1. K8s介绍:容器世界的"交响乐指挥家"
为什么需要Kubernetes?
问题: 你有了Docker,可以在一台机器上跑几十个容器。但如果有10台机器、100个服务呢?手动管理太累了!
类比: Docker就像每位乐手自己练习,Kubernetes就是指挥家——它告诉每位乐手什么时候开始、什么时候停止,确保整个交响乐团协调一致。
K8s能做什么?
- 自动部署和回滚: 一键部署100个容器,一键升级/回退
- 自动修复: 容器挂了?自动重启。节点挂了?自动迁移到其他节点
- 自动伸缩: 访问量大了?自动增加副本。访问量小了?自动缩减
- 负载均衡: 自动将流量分发到健康的容器
- 服务发现: 容器之间通过名字就能找到对方
K8s的架构
┌─────────────────────────────────────────────────────┐
│ Master节点(大脑) │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │API Server│ │Scheduler │ │Controller Manager │ │
│ │(对外接口) │ │(调度决策) │ │(状态维护) │ │
│ └──────────┘ └──────────┘ └───────────────────┘ │
│ ┌──────────┐ │
│ │ etcd │ ← 集群的"记事本",存储所有状态 │
│ └──────────┘ │
└─────────────────────────────────────────────────────┘
│
├──→ ┌─────────────────────────┐
│ │ Node节点(工人) │
│ │ ┌──────┐ ┌──────────┐ │
│ │ │kubelet│ │kube-proxy│ │
│ │ │(管家) │ │(网络代理)│ │
│ │ └──────┘ └──────────┘ │
│ │ ┌──────────────────┐ │
│ │ │ Pod Pod Pod Pod │ │
│ │ └──────────────────┘ │
│ └─────────────────────────┘
│
└──→ ┌─────────────────────────┐
│ Node节点(工人) │
│ ... │
└─────────────────────────┘
核心组件说明:
| 组件 | 位置 | 功能 | 类比 |
|---|---|---|---|
| API Server | Master | 集群的统一入口,接收所有请求 | 前台接待 |
| etcd | Master | 存储集群所有状态数据 | 记事本/数据库 |
| Scheduler | Master | 决定Pod运行在哪个Node上 | HR分配工位 |
| Controller Manager | Master | 确保实际状态=期望状态 | 质检员 |
| kubelet | Node | 管理本节点上的Pod和容器 | 车间主任 |
| kube-proxy | Node | 维护网络规则,实现负载均衡 | 邮递员 |
2. YAML基础:写给K8s的"说明书"
为什么是YAML?
K8s用YAML文件来描述"我想要什么"——创建什么资源、多少个副本、用什么镜像。
类比: YAML就像你写给指挥家的乐谱——你只需要写清楚"要3个小提琴、2个大提琴",指挥家会帮你安排好一切。
YAML语法要点
# 键值对(注意冒号后面有空格)
name: my-app
version: 1.0
# 列表(用 - 表示)
fruits:
- apple
- banana
- orange
# 嵌套对象(用缩进表示层级,只能用空格,不能用Tab!)
server:
host: 192.168.1.1
port: 8080
config:
debug: true
log_level: info
# 多行字符串
description: |
这是一个多行
文本内容,保留换行符
# 单行字符串(折叠换行)
summary: >
这是单行文本
换行会被折叠
一个完整的K8s YAML示例
apiVersion: apps/v1 # API版本
kind: Deployment # 资源类型
metadata: # 元数据
name: nginx-deployment # 名称
labels: # 标签
app: nginx
spec: # 规格定义
replicas: 3 # 副本数
selector: # 选择器
matchLabels:
app: nginx
template: # Pod模板
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx # 容器名
image: nginx:1.21 # 镜像
ports:
- containerPort: 80 # 容器端口
常见YAML错误
| 错误 | 示例 | 正确 |
|---|---|---|
| 使用了Tab缩进 | \tname: app | name: app(空格) |
| 冒号后缺少空格 | name:app | name: app |
| 缩进层级不一致 | 混用2空格和4空格 | 统一使用2空格 |
3. Master与Node部署
环境准备
| 主机 | IP | 角色 |
|---|---|---|
| master | 192.168.0.200 | Master节点 |
| node1 | 192.168.0.201 | Worker节点 |
| node2 | 192.168.0.202 | Worker节点 |
所有节点都要执行:
# 1. 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 2. 关闭SELinux
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
# 3. 关闭Swap分区(K8s要求)
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
# 4. 配置内核参数
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
modprobe br_netfilter
sysctl -p /etc/sysctl.d/k8s.conf
# 5. 安装Docker(参照第一部分)
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum -y install docker-ce
systemctl start docker && systemctl enable docker
# 6. 安装kubeadm、kubelet、kubectl
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
EOF
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet
Master节点初始化
# 初始化集群
[root@master ~]# kubeadm init \
--kubernetes-version=v1.22.0 \
--pod-network-cidr=10.244.0.0/16 \
--image-repository=registry.aliyuncs.com/google_containers
# 成功后会输出如下信息:
Your Kubernetes master has initialized successfully!
# 配置kubectl访问(root用户)
export KUBECONFIG=/etc/kubernetes/admin.conf
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
source ~/.bash_profile
# 或者(普通用户)
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 安装网络插件(Flannel)
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
Node节点加入集群
# 使用kubeadm init输出的join命令
[root@node1 ~]# kubeadm join 192.168.0.200:6443 \
--token h4lewk.4vex9tcwtdwfkn05 \
--discovery-token-ca-cert-hash sha256:7118c498...
# 如果token过期(默认24小时),在Master上重新生成
[root@master ~]# kubeadm token create --print-join-command
验证集群状态
# 查看所有节点
[root@master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready master 50m v1.22.0
node1 Ready <none> 16m v1.22.0
node2 Ready <none> 10m v1.22.0
# 查看集群信息
[root@master ~]# kubectl cluster-info
Kubernetes master is running at https://192.168.0.200:6443
CoreDNS is running at https://192.168.0.200:6443/api/v1/...
# 查看各组件健康状态
[root@master ~]# kubectl get cs
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health": "true"}
4. kubectl使用:与集群对话
kubectl命令分类
# 查看帮助
kubectl --help
# 查看子命令帮助
kubectl get --help
最常用的命令
# ===== 查看资源 =====
kubectl get pods # 查看Pod
kubectl get pods -o wide # 更多信息(含IP和Node)
kubectl get pods --all-namespaces # 所有命名空间
kubectl get deployments # 查看Deployment
kubectl get services # 查看Service
kubectl get nodes # 查看节点
kubectl get all # 查看所有资源
# ===== 详细描述 =====
kubectl describe pod <pod-name> # Pod详细信息
kubectl describe service <svc-name> # Service详细信息
kubectl describe node <node-name> # 节点详细信息
# ===== 创建/更新资源 =====
kubectl apply -f nginx.yaml # 创建或更新资源
kubectl create -f nginx.yaml # 仅创建
# ===== 删除资源 =====
kubectl delete pod <pod-name> # 删除Pod
kubectl delete -f nginx.yaml # 按文件删除
kubectl delete deployment nginx # 删除Deployment
# ===== 调试和排错 =====
kubectl logs <pod-name> # 查看Pod日志
kubectl logs -f <pod-name> # 实时跟踪日志
kubectl exec -it <pod-name> -- /bin/bash # 进入Pod
kubectl top pods # 查看Pod资源使用
kubectl top nodes # 查看节点资源使用
# ===== 扩缩容 =====
kubectl scale deployment nginx --replicas=5 # 扩到5个副本
# ===== 标签和注解 =====
kubectl label pod my-pod env=prod # 添加标签
kubectl get pods -l env=prod # 按标签筛选
实战:创建一个简单的应用
# 方法一:命令行快速创建
[root@master ~]# kubectl run httpd-app --image=httpd --replicas=2
# 方法二:YAML文件(推荐)
[root@master ~]# cat nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
[root@master ~]# kubectl apply -f nginx-deploy.yaml
deployment.apps/nginx-deploy created
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS IP NODE
nginx-deploy-5fbccd7c6c-2fq46 1/1 Running 10.244.1.3 node1
nginx-deploy-5fbccd7c6c-szzn7 1/1 Running 10.244.1.2 node2
[root@master ~]# curl 10.244.1.3
<html><body><h1>Welcome to nginx!</h1></body></html>
5. Dashboard:集群的Web管理界面
部署Dashboard
# 下载Dashboard配置文件
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml
# 修改Service类型为NodePort(使外部可以访问)
# 编辑recommended.yaml中的Service部分:
kind: Service
apiVersion: v1
metadata:
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
type: NodePort # 改为NodePort
ports:
- port: 443
targetPort: 8443
nodePort: 32666 # 指定外部访问端口
selector:
k8s-app: kubernetes-dashboard
# 部署
[root@master ~]# kubectl apply -f recommended.yaml
# 查看Pod状态
[root@master ~]# kubectl get pods --all-namespaces | grep dashboard
kubernetes-dashboard kubernetes-dashboard-cfc74c45b-tfsx9 1/1 Running 0 6m
创建管理员账号
# 创建管理员ServiceAccount
cat > dashboard-admin.yaml << 'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
name: dashboard-admin
namespace: kubernetes-dashboard
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dashboard-admin
subjects:
- kind: ServiceAccount
name: dashboard-admin
namespace: kubernetes-dashboard
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
EOF
[root@master ~]# kubectl apply -f dashboard-admin.yaml
# 获取登录Token
[root@master ~]# kubectl -n kubernetes-dashboard describe secret \
$(kubectl -n kubernetes-dashboard get secret | grep dashboard-admin | awk '{print $1}')
...
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9...(复制这串Token)
访问Dashboard
浏览器打开:https://<NodeIP>:32666
输入上面获取的Token登录即可看到集群管理界面。
6. Pod:K8s的最小调度单元
Pod是什么?
类比: Pod就像一间公寓单元房——里面可以住一到几个人(容器),他们共享水电(网络和存储),但各有各的房间。
Pod是K8s中最小的调度单位,一个Pod可以包含一个或多个容器。
单容器Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
多容器Pod(Sidecar模式)
apiVersion: v1
kind: Pod
metadata:
name: web-with-logger
spec:
containers:
- name: web
image: nginx:1.21
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: logger # Sidecar容器:负责收集日志
image: busybox
command: ['sh', '-c', 'tail -f /var/log/nginx/access.log']
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
volumes:
- name: shared-logs
emptyDir: {}
Pod的生命周期
Pending → Running → Succeeded/Failed
健康检查(Probes)
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-probes
spec:
containers:
- name: nginx
image: nginx:1.21
# 存活探针:检查容器是否存活
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10 # 启动后10秒开始检查
periodSeconds: 5 # 每5秒检查一次
# 就绪探针:检查容器是否准备好接收流量
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 3
重启策略
spec:
restartPolicy: Always # 总是重启(默认)
# restartPolicy: OnFailure # 失败时重启
# restartPolicy: Never # 从不重启
7. ReplicaSet与Deployment:副本管理与滚动更新
ReplicaSet:确保副本数量
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-rs
spec:
replicas: 3 # 始终保持3个副本
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
注意: 实际使用中很少直接创建ReplicaSet,而是通过Deployment来管理。
Deployment:推荐的生产方式
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 3
strategy:
type: RollingUpdate # 滚动更新策略
rollingUpdate:
maxSurge: 1 # 最多多出1个Pod
maxUnavailable: 0 # 不允许有Pod不可用
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
常用操作
# 创建Deployment
[root@master ~]# kubectl apply -f nginx-deploy.yaml
# 查看Deployment
[root@master ~]# kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 3/3 3 3 2m
# 扩容
[root@master ~]# kubectl scale deployment nginx-deploy --replicas=5
deployment.apps/nginx-deploy scaled
# 更新镜像(触发滚动更新)
[root@master ~]# kubectl set image deployment/nginx-deploy nginx=nginx:1.22
deployment.apps/nginx-deploy image updated
# 查看滚动更新状态
[root@master ~]# kubectl rollout status deployment/nginx-deploy
deployment "nginx-deploy" successfully rolled out
# 回滚到上一个版本
[root@master ~]# kubectl rollout undo deployment/nginx-deploy
deployment.apps/nginx-deploy rolled back
# 查看历史版本
[root@master ~]# kubectl rollout history deployment/nginx-deploy
REVISION CHANGE-CAUSE
1 <none>
2 <none>
滚动更新的原理
旧版本:Pod1(v1) Pod2(v1) Pod3(v1)
↓
更新中: Pod1(v1) Pod2(v1) Pod3(v2) ← 新建1个v2,删1个v1
↓
更新中: Pod1(v1) Pod2(v2) Pod3(v2) ← 继续
↓
完成: Pod1(v2) Pod2(v2) Pod3(v2) ← 全部更新为v2
8. Service:让外部用户找到你的服务
问题
问题: Pod的IP是动态的(重启就变),而且外部无法直接访问Pod IP。怎么让用户稳定地访问服务?
类比: Service就像一个"总机号码"——不管员工(Pod)怎么换工位,打总机号码总能转接到对的人。
Service的三种端口
| 端口类型 | 说明 |
|---|---|
| targetPort | 容器内的端口(Pod的端口) |
| port | Service在集群内的端口(:port) |
| nodePort | Service在节点上的端口(外部访问用) |
ClusterIP(默认,仅集群内访问)
apiVersion: v1
kind: Service
metadata:
name: nginx-clusterip
spec:
type: ClusterIP
selector:
app: nginx # 选择标签为app=nginx的Pod
ports:
- port: 80 # Service端口
targetPort: 80 # Pod端口
NodePort(外部可访问)
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80 # 集群内Service端口
targetPort: 80 # Pod端口
nodePort: 30080 # 节点端口(范围30000-32767)
[root@master ~]# kubectl apply -f nginx-svc.yaml
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP PORT(S) AGE
nginx-nodeport NodePort 10.96.100.50 80:30080/TCP 10s
# 外部访问:http://<任意NodeIP>:30080
LoadBalancer(云环境自动分配外部IP)
apiVersion: v1
kind: Service
metadata:
name: nginx-lb
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
ExternalName(别名映射)
apiVersion: v1
kind: Service
metadata:
name: my-db
spec:
type: ExternalName
externalName: db.example.com # 将my-db映射到外部域名
9. Volume:K8s中的数据持久化
emptyDir:临时共享空间
Pod创建时创建,Pod删除时数据丢失。适合容器间临时共享数据。
volumes:
- name: shared-data
emptyDir: {}
hostPath:挂载宿主机目录
volumes:
- name: host-volume
hostPath:
path: /opt/data # 宿主机目录
type: DirectoryOrCreate
PersistentVolume(PV)与PersistentVolumeClaim(PVC)
类比:
- PV = 学校图书馆的书架(由管理员预先创建)
- PVC = 你的借书申请(你告诉系统你需要多大的空间)
- K8s自动把你的PVC和合适的PV绑定
# PV定义(管理员创建)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
spec:
capacity:
storage: 5Gi # 容量
accessModes:
- ReadWriteOnce # 读写权限,只能被单个Node挂载
nfs:
server: 192.168.0.100 # NFS服务器地址
path: /opt/nfsshare # 共享路径
---
# PVC定义(用户创建)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi # 申请5GB空间
---
# Pod引用PVC
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- mountPath: /data
name: my-volume
volumes:
- name: my-volume
persistentVolumeClaim:
claimName: my-pvc
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES STATUS CLAIM AGE
pv001 5Gi RWO Bound default/my-pvc 2m
[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES AGE
my-pvc Bound pv001 5Gi RWO 2m
ConfigMap:配置文件管理
# 从文件创建ConfigMap
[root@master ~]# kubectl create configmap nginx-config \
--from-file=nginx.conf=/etc/nginx/nginx.conf
# 在Pod中使用ConfigMap
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-config
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
volumes:
- name: config-volume
configMap:
name: nginx-config
Secret:敏感信息管理
# 创建Secret
[root@master ~]# kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password=S3cur3P@ss
# 在Pod中使用Secret作为环境变量
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
10. Job与CronJob:批处理与定时任务
Job:一次性任务
类比: 就像期末考试的阅卷任务——完成后就结束了,不需要一直运行。
apiVersion: batch/v1
kind: Job
metadata:
name: math-calculation
spec:
completions: 3 # 总共需要完成3次
parallelism: 2 # 最多同时运行2个
backoffLimit: 3 # 失败重试最多3次
template:
spec:
containers:
- name: calculator
image: python:3.9
command: ["python", "-c", "print('计算完成!结果: 42')"]
restartPolicy: Never
[root@master ~]# kubectl apply -f job.yaml
[root@master ~]# kubectl get jobs
NAME COMPLETIONS DURATION AGE
math-calculation 3/3 30s 2m
[root@master ~]# kubectl logs math-calculation-xxx
计算完成!结果: 42
CronJob:定时任务
类比: 就像每周一早上自动收作业一样。
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-backup
spec:
schedule: "0 2 * * *" # 每天凌晨2点执行
# cron表达式:分 时 日 月 星期
# "*/5 * * * *" = 每5分钟
# "0 9 * * 1" = 每周一9点
successfulJobsHistoryLimit: 3 # 保留最近3个成功的Job
failedJobsHistoryLimit: 1 # 保留最近1个失败的Job
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: busybox
command: ["sh", "-c", "echo '备份完成: $(date)'"]
restartPolicy: Never
[root@master ~]# kubectl apply -f cronjob.yaml
[root@master ~]# kubectl get cronjobs
NAME SCHEDULE LAST SCHEDULE AGE
daily-backup 0 2 * * * 2h ago 1d
11. DaemonSet:每个节点的"管家"
什么是DaemonSet?
类比: 就像每栋教学楼都有一个保洁员——确保每个Node上都运行且只运行一个Pod副本。
典型应用场景:
- 日志收集(Fluentd、Filebeat)
- 系统监控(Prometheus Node Exporter)
- 网络插件(Flannel、Calico)
- 存储守护进程
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
containers:
- name: node-exporter
image: prom/node-exporter:v1.3.1
ports:
- containerPort: 9100
hostPort: 9100 # 使用宿主机端口
[root@master ~]# kubectl apply -f daemonset.yaml
[root@master ~]# kubectl get daemonset -n monitoring
NAME DESIRED CURRENT READY NODES AGE
node-exporter 3 3 3 3 2m
# DESIRED=3表示3个节点各运行1个Pod
12. HPA:自动弹性伸缩
什么是HPA?
类比: 就像学校食堂——中午人多的时候多开几个窗口,人少的时候关掉多余窗口,节省人力。
HPA(Horizontal Pod Autoscaler)根据CPU或内存使用率,自动增减Pod副本数。
前提条件
需要安装metrics-server来采集资源使用数据:
# 安装metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 验证
[root@master ~]# kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master 150m 7% 1024Mi 52%
node1 80m 4% 512Mi 26%
node2 90m 4% 480Mi 24%
创建HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deploy
minReplicas: 2 # 最少2个副本
maxReplicas: 10 # 最多10个副本
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50 # CPU使用率超过50%则扩容
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存使用率超过80%则扩容
或者用命令行快速创建:
[root@master ~]# kubectl autoscale deployment nginx-deploy \
--min=2 --max=10 --cpu-percent=50
[root@master ~]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
nginx-hpa Deployment/nginx-deploy 20%/50% 2 10 3
弹性伸缩的过程
正常:CPU 20% → 3个Pod(正常)
↓ 流量高峰
CPU 70% → HPA检测到超阈值 → 扩到5个Pod
↓ 流量恢复
CPU 30% → HPA检测到低于阈值 → 缩回3个Pod(等待冷却期后)
总结:Docker与K8s知识图谱
Docker(单机容器管理)
├── 核心概念
│ ├── 镜像(Image)—— 菜谱
│ ├── 容器(Container)—— 做出来的菜
│ └── 仓库(Registry)—— 菜谱大全
├── 底层技术
│ ├── Namespace → 隔离(PID/NET/MNT/UTS/IPC/USER)
│ ├── Cgroup → 限制(CPU/内存/IO)
│ └── OverlayFS → 分层文件系统
└── 网络
├── bridge/host/none/container模式
├── 端口映射(-p/-P)
└── 跨主机通讯(Flannel/直接路由)
Kubernetes(集群容器编排)
├── 架构
│ ├── Master: API Server + Scheduler + Controller + etcd
│ └── Node: kubelet + kube-proxy + Pod
├── 核心资源
│ ├── Pod → 最小调度单元
│ ├── Deployment → 副本管理 + 滚动更新
│ ├── Service → 服务发现 + 负载均衡
│ └── Volume → 数据持久化(PV/PVC/ConfigMap/Secret)
├── 工作负载
│ ├── Job/CronJob → 批处理/定时任务
│ └── DaemonSet → 每节点一个
└── 高级功能
└── HPA → 自动弹性伸缩
常见问题FAQ
Docker相关
Q1:容器和虚拟机到底该选哪个?
- 需要强隔离、运行不同操作系统 → 虚拟机
- 需要快速部署、高密度、微服务 → 容器
Q2:docker run之后容器立即退出?
- 原因:容器的前台进程执行完毕了
- 解决:确保有持续运行的前台进程,如
nginx -g "daemon off;"
Q3:如何查看容器的IP地址?
docker inspect <容器名> | grep IPAddress
Kubernetes相关
Q4:Pod一直处于Pending状态?
kubectl describe pod <pod-name>查看Events- 常见原因:资源不足、镜像拉取失败、PVC未绑定
Q5:Service访问不通?
- 检查Pod是否Running:
kubectl get pods -l app=<label> - 检查Service选择器:
kubectl describe svc <svc-name> - 检查kube-proxy是否正常:
kubectl get pods -n kube-system | grep proxy
Q6:如何查看某个Pod在哪个Node上运行?
kubectl get pods -o wide