Kubernetes教程
# Kubernetes
# Kubernetes 简介
# 概述

Kubernetes 是 Google 2014 年创建管理的,是 Google 10 多年大规模容器管理技术 Borg 的开源版本。
Kubernetes 是容器集群管理系统,是一个开源的平台,可以实现容器集群的自动化部署、自动扩缩容、维护等功能。使用 Kubernetes 我们可以:
- 快速部署应用
- 快速扩展应用
- 无缝对接新的应用功能
- 节省资源,优化硬件资源的使用
Kubernetes 的目标是促进完善组件和工具的生态系统,以减轻应用程序在公有云或私有云中运行的负担。
# 特点
- 可移植: 支持公有云,私有云,混合云,多重云(多个公共云)
- 可扩展: 模块化,插件化,可挂载,可组合
- 自动化: 自动部署,自动重启,自动复制,自动伸缩/扩展
# 从传统到容器化部署

# 传统的部署方式
传统的应用部署方式是通过插件或脚本来安装应用。这样做的缺点是应用的运行、配置、管理、所有生存周期将与当前操作系统绑定,这样做并不利于应用的升级更新/回滚等操作,当然也可以通过创建虚机的方式来实现某些功能,但是虚拟机非常重,并不利于可移植性。
# 容器化部署的优势
- 快速创建/部署应用: 与虚拟机相比,容器镜像的创建更加容易。
- 持续开发、集成和部署: 提供可靠且频繁的容器镜像构建/部署,并使用快速和简单的回滚(由于镜像不可变性)。
- 开发和运行相分离: 在 build 或者 release 阶段创建容器镜像,使得应用和基础设施解耦。
- 开发,测试和生产环境一致性: 在本地或外网(生产环境)运行的一致性。
- 云平台或其他操作系统: 可以在 Ubuntu、RHEL、CoreOS、on-prem、Google Container Engine 或其它任何环境中运行。
- 分布式,弹性,微服务化: 应用程序分为更小的、独立的部件,可以动态部署和管理。
- 资源隔离
- 资源利用更高效
# 为什么需要 Kubernetes
可以在物理或虚拟机的 Kubernetes 集群上运行容器化应用,Kubernetes 能提供一个以 “容器为中心的基础架构”,满足在生产环境中运行应用的一些常见需求,如:
- 多个进程协同工作
- 存储系统挂载
- 应用健康检查
- 应用实例的复制
- 自动伸缩/扩展
- 注册与发现
- 负载均衡
- 滚动更新
- 资源监控
- 日志访问
- 调试应用程序
- 提供认证和授权
# Kubernetes 安装前的准备
# 概述
本次安装采用 Ubuntu Server X64 18.04 LTS 版本安装 kubernetes 集群环境,集群节点为 1 主 2 从模式,此次对虚拟机会有些基本要求,如下:
- OS:Ubuntu Server X64 18.04 LTS(16.04 版本步骤相同,再之前则不同)
- CPU:最低要求,1 CPU 2 核
- 内存:最低要求,2 GB
- 磁盘:最低要求,20 GB
# 节点配置
| 主机名 | IP | 角色 | 系统 | CPU/内存 | 磁盘 |
|---|---|---|---|---|---|
| kubernetes-master | 192.168.141.110 | Master | Ubuntu Server 18.04 | 2 核 2G | 20G |
| kubernetes-node-01 | 192.168.141.120 | Node | Ubuntu Server 18.04 | 2 核 4G | 20G |
| kubernetes-node-02 | 192.168.141.121 | Node | Ubuntu Server 18.04 | 2 核 4G | 20G |
# 统一环境配置
注意: 以下步骤请在制作 VMware 镜像时一并完成,避免逐台安装的痛苦
# 关闭交换空间
swapoff -a
# 避免开机启动交换空间
# 注释 swap 开头的行vi /etc/fstab
# 关闭防火墙
ufw disable
# 配置 DNS
# 取消 DNS 行注释,并增加 DNS 配置如:114.114.114.114,修改后重启下计算机
vi /etc/systemd/resolved.conf
2
# 安装 Docker
# 更新软件源
sudo apt-get update
# 安装所需依赖
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# 安装 GPG 证书
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# 新增软件源信息
sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# 再次更新软件源
sudo apt-get -y update
# 安装 Docker CE 版
sudo apt-get -y install docker-ce
2
3
4
5
6
7
8
9
10
11
12
# 配置 Docker 加速器
注意: 国内镜像加速器可能会很卡,请替换成你自己阿里云镜像加速器,地址如:
https://yourself.mirror.aliyuncs.com,在阿里云控制台的 容器镜像服务 -> 镜像加速器 菜单中可以找到
在 /etc/docker/daemon.json 中写入如下内容(以下配置修改 cgroup 驱动为 systemd,满足 K8S 建议)
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"registry-mirrors": [
"https://yjrxjjt7.mirror.aliyuncs.com",
"https://dockerhub.azk8s.cn",
"https://registry.docker-cn.com"
],
"storage-driver": "overlay2"
}
2
3
4
5
6
7
8
9
10
11
12
13
# 安装 Kubernetes 必备工具
安装三个 Kubernetes 必备工具,分别为 kubeadm,kubelet,kubectl
# 安装系统工具
apt-get update && apt-get install -y apt-transport-https
# 安装 GPG 证书
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
# 写入软件源;注意:我们用系统代号为 bionic,但目前阿里云不支持,所以沿用 16.04 的 xenial
cat << EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
# 安装
apt-get update && apt-get install -y kubelet kubeadm kubectl
2
3
4
5
6
7
8
9
10
# 同步时间
- 设置时区
dpkg-reconfigure tzdata
- 选择 Asia(亚洲)

- 选择 Shanghai(上海)

- 时间同步
# 安装 ntpdate
apt-get install ntpdate
# 设置系统时间与网络时间同步(cn.pool.ntp.org 位于中国的公共 NTP 服务器)
ntpdate cn.pool.ntp.org
# 将系统时间写入硬件时间
hwclock --systohc
2
3
4
5
6
- 确认时间
date
# 输出如下(自行对照与系统时间是否一致)
Sun Jun 2 22:02:35 CST 2019
2
3
# 修改 cloud.cfg
主要作用是防止重启后主机名还原
vi /etc/cloud/cloud.cfg
# 该配置默认为 false,修改为 true 即可
preserve_hostname: true
2
3
# 单独节点配置
注意: 为 Master 和 Node 节点单独配置对应的 IP 和 主机名
# 配置 IP
编辑 vi /etc/netplan/50-cloud-init.yaml 配置文件,修改内容如下
kubernetes-master
network:
ethernets:
ens33:
addresses: [192.168.12.110/24]
gateway4: 192.168.12.2
nameservers:
addresses: [192.168.12.2]
version: 2
#kubernetes-node-01
network:
ethernets:
ens33:
addresses: [192.168.12.120/24]
gateway4: 192.168.12.2
nameservers:
addresses: [192.168.12.2]
version: 2
#kubernetes-node-02
network:
ethernets:
ens33:
addresses: [192.168.12.121/24]
gateway4: 192.168.12.2
nameservers:
addresses: [192.168.12.2]
version: 2
#kubernetes-node-03
network:
ethernets:
ens33:
addresses: [192.168.12.122/24]
gateway4: 192.168.12.2
nameservers:
addresses: [192.168.12.2]
version: 2
#kubernetes-volums
network:
ethernets:
ens33:
addresses: [192.168.12.130/24]
gateway4: 192.168.12.2
nameservers:
addresses: [192.168.12.2]
version: 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
使用 netplan apply 命令让配置生效
# 配置主机名
#kubernetes-master
# 修改主机名
hostnamectl set-hostname kubernetes-master
# 配置 hosts
cat >> /etc/hosts << EOF
192.168.12.110 kubernetes-master
EOF
#kubernetes-node-01
# 修改主机名
hostnamectl set-hostname kubernetes-node-01
# 配置 hosts
cat >> /etc/hosts << EOF
192.168.12.120 kubernetes-node-01
EOF
#kubernetes-node-02
# 修改主机名
hostnamectl set-hostname kubernetes-node-02
# 配置 hosts
cat >> /etc/hosts << EOF
192.168.12.121 kubernetes-node-02
EOF
#kubernetes-node-03
# 修改主机名
hostnamectl set-hostname kubernetes-node-03
# 配置 hosts
cat >> /etc/hosts << EOF
192.168.12.122 kubernetes-node-03
EOF
#kubernetes-volums
# 修改主机名
hostnamectl set-hostname kubernetes-volums
# 配置 hosts
cat >> /etc/hosts << EOF
192.168.12.130 kubernetes-volums
EOF
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Kubernetes 安装集群
# 概述
kubeadm 是 kubernetes 的集群安装工具,能够快速安装 kubernetes 集群,安装 kubernetes 主要是安装它的各个镜像,而 kubeadm 已经为我们集成好了运行 kubernetes 所需的基本镜像。但由于国内的网络原因,在搭建环境时,无法拉取到这些镜像。此时我们只需要修改为阿里云提供的镜像服务即可解决该问题。
# 创建并修改配置
# 导出配置文件
kubeadm config print init-defaults --kubeconfig ClusterConfiguration > kubeadm.yml
2
apiVersion: kubeadm.k8s.io/v1beta2
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
# 修改为主节点 IP
advertiseAddress: 192.168.12.110
bindPort: 6443
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: kubernetes-master
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta2
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns:
type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
# 国内不能访问 Google,修改为阿里云
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
# 修改版本号
kubernetesVersion: v1.15.0
networking:
dnsDomain: cluster.local
# 配置 POD 所在网段为我们虚拟机不重叠的网段(这里用的是 Flannel 默认网段)
podSubnet: "10.244.0.0/16"
serviceSubnet: 10.96.0.0/12
scheduler: {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 查看所需镜像
kubeadm config images list --config kubeadm.yml
# 输出如下
registry.aliyuncs.com/google_containers/kube-apiserver:v1.15.0
registry.aliyuncs.com/google_containers/kube-controller-manager:v1.15.0
registry.aliyuncs.com/google_containers/kube-scheduler:v1.15.0
registry.aliyuncs.com/google_containers/kube-proxy:v1.15.0
registry.aliyuncs.com/google_containers/pause:3.1
registry.aliyuncs.com/google_containers/etcd:3.3.10
registry.aliyuncs.com/google_containers/coredns:1.3.1
2
3
4
5
6
7
8
9
# 拉取所需镜像
kubeadm config images pull --config kubeadm.yml
# 输出如下
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.15.0
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.15.0
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.15.0
[config/images] Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.15.0
[config/images] Pulled registry.aliyuncs.com/google_containers/pause:3.1
[config/images] Pulled registry.aliyuncs.com/google_containers/etcd:3.3.10
[config/images] Pulled registry.aliyuncs.com/google_containers/coredns:1.3.1
2
3
4
5
6
7
8
9
# 安装主节点
执行以下命令初始化主节点,该命令指定了初始化时需要使用的配置文件,其中添加 --upload-certs 参数可以在后续执行加入节点时自动分发证书文件。追加的 tee kubeadm-init.log 用以输出日志。
注意: 如果安装 kubernetes 版本和下载的镜像版本不统一则会出现
timed out waiting for the condition错误。中途失败或是想修改配置可以使用kubeadm reset命令重置配置,再做初始化操作即可。
kubeadm init --config=kubeadm.yml --upload-certs | tee kubeadm-init.log
# 输出如下
Flag --experimental-upload-certs has been deprecated, use --upload-certs instead
[init] Using Kubernetes version: v1.15.0
[preflight] Running pre-flight checks
[WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.141.110]
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [kubernetes-master localhost] and IPs [192.168.141.110 127.0.0.1 ::1]
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [kubernetes-master localhost] and IPs [192.168.141.110 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 23.006217 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.15" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
4a351218cf34c15b04b08b7d916c27a625418145d8d3b281ec125fd2b2ac7ab5
[mark-control-plane] Marking the node kubernetes-master as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node kubernetes-master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: abcdef.0123456789abcdef
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.12.110:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:755175060aa59ee66637df437cf8d3b461ac403adaa6ab341f3ba28e26c79bef
#生成的日志 cat kubeadm-init.log
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
kubeadm join 192.168.12.110:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:755175060aa59ee66637df437cf8d3b461ac403adaa6ab341f3ba28e26c79bef
#将以上两句话复制到node 结点运行进行集群
2
3
4
# 配置 kubectl
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# 非 ROOT 用户执行
chown $(id -u):$(id -g) $HOME/.kube/config
2
3
4
# 验证是否成功
kubectl get node
# 输出如下
NAME STATUS ROLES AGE VERSION
kubernetes-master NotReady master 4m38s v1.15.0
2
3
4
# 安装从节点
将 Node 节点加入到集群中很简单,只需要在 Node 服务器上安装 kubeadm,kubectl,kubelet 三个工具,然后使用 kubeadm join 命令加入即可
#如果token 过期则重新获取
kubeadm token create
2
3
kubeadm join 192.168.12.110:6443 --token 51m7dx.x88i64jzhvavb91d \
--discovery-token-ca-cert-hash sha256:755175060aa59ee66637df437cf8d3b461ac403adaa6ab341f3ba28e26c79bef
# 输出如下
[preflight] Running pre-flight checks
[WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.15" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 验证是否成功
回到 Master 主节点查看是否安装成功
注意: 如果 Node 节点加入 Master 时配置有问题可以在 Node 节点上使用
kubeadm reset重置配置再使用kubeadm join命令重新加入即可。希望在 master 节点删除 node ,可以使用kubectl delete nodes删除。
kubectl get node
# 输出如下
NAME STATUS ROLES AGE VERSION
kubernetes-master NotReady master 20m v1.15.0
kubernetes-node-01 NotReady <none> 16s v1.15.0
kubernetes-node-02 NotReady <none> 6s v1.15.0
2
3
4
5
6
# 查看 Pod 状态
coredns 尚未运行,此时我们还需要安装网络插件
kubectl get pod -n kube-system -o wide
# 输出如下
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-bccdc95cf-9s4bm 0/1 Pending 0 24m <none> <none> <none> <none>
coredns-bccdc95cf-s8ggd 0/1 Pending 0 24m <none> <none> <none> <none>
etcd-kubernetes-master 1/1 Running 0 24m 192.168.141.110 kubernetes-master <none> <none>
kube-apiserver-kubernetes-master 1/1 Running 0 24m 192.168.141.110 kubernetes-master <none> <none>
kube-controller-manager-kubernetes-master 1/1 Running 0 23m 192.168.141.110 kubernetes-master <none> <none>
kube-proxy-8s87d 1/1 Running 0 4m56s 192.168.141.120 kubernetes-node-01 <none> <none>
kube-proxy-cbnlb 1/1 Running 0 4m46s 192.168.141.121 kubernetes-node-02 <none> <none>
kube-proxy-vwhxj 1/1 Running 0 24m 192.168.141.110 kubernetes-master <none> <none>
kube-scheduler-kubernetes-master 1/1 Running 0 24m 192.168.141.110 kubernetes-master <none> <none>
2
3
4
5
6
7
8
9
10
11
12
# 附:扩展阅读
# kubeadm init 的执行过程
- init: 指定版本进行初始化操作
- preflight: 初始化前的检查和下载所需要的 Docker 镜像文件
- kubelet-start: 生成 kubelet 的配置文件
var/lib/kubelet/config.yaml,没有这个文件 kubelet 无法启动,所以初始化之前的 kubelet 实际上启动不会成功 - certificates: 生成 Kubernetes 使用的证书,存放在
/etc/kubernetes/pki目录中 - kubeconfig: 生成 KubeConfig 文件,存放在
/etc/kubernetes目录中,组件之间通信需要使用对应文件 - control-plane: 使用
/etc/kubernetes/manifest目录下的 YAML 文件,安装 Master 组件 - etcd: 使用
/etc/kubernetes/manifest/etcd.yaml安装 Etcd 服务 - wait-control-plane: 等待 control-plan 部署的 Master 组件启动
- apiclient: 检查 Master 组件服务状态。
- uploadconfig: 更新配置
- kubelet: 使用 configMap 配置 kubelet
- patchnode: 更新 CNI 信息到 Node 上,通过注释的方式记录
- mark-control-plane: 为当前节点打标签,打了角色 Master,和不可调度标签,这样默认就不会使用 Master 节点来运行 Pod
- bootstrap-token: 生成 token 记录下来,后边使用
kubeadm join往集群中添加节点时会用到 - addons: 安装附加组件 CoreDNS 和 kube-proxy
# Kubernetes 配置网络
# 概述
容器网络是容器选择连接到其他容器、主机和外部网络的机制。容器的 runtime 提供了各种网络模式,每种模式都会产生不同的体验。例如,Docker 默认情况下可以为容器配置以下网络:
- none: 将容器添加到一个容器专门的网络堆栈中,没有对外连接。
- host: 将容器添加到主机的网络堆栈中,没有隔离。
- default bridge: 默认网络模式。每个容器可以通过 IP 地址相互连接。
- 自定义网桥: 用户定义的网桥,具有更多的灵活性、隔离性和其他便利功能。
# 什么是 CNI
CNI(Container Network Interface) 是一个标准的,通用的接口。在容器平台,Docker,Kubernetes,Mesos 容器网络解决方案 flannel,calico,weave。只要提供一个标准的接口,就能为同样满足该协议的所有容器平台提供网络功能,而 CNI 正是这样的一个标准接口协议。
# Kubernetes 中的 CNI 插件
CNI 的初衷是创建一个框架,用于在配置或销毁容器时动态配置适当的网络配置和资源。插件负责为接口配置和管理 IP 地址,并且通常提供与 IP 管理、每个容器的 IP 分配、以及多主机连接相关的功能。容器运行时会调用网络插件,从而在容器启动时分配 IP 地址并配置网络,并在删除容器时再次调用它以清理这些资源。
运行时或协调器决定了容器应该加入哪个网络以及它需要调用哪个插件。然后,插件会将接口添加到容器网络命名空间中,作为一个 veth 对的一侧。接着,它会在主机上进行更改,包括将 veth 的其他部分连接到网桥。再之后,它会通过调用单独的 IPAM(IP地址管理)插件来分配 IP 地址并设置路由。
在 Kubernetes 中,kubelet 可以在适当的时间调用它找到的插件,为通过 kubelet 启动的 pod进行自动的网络配置。
Kubernetes 中可选的 CNI 插件如下:
- Flannel
- Calico
- Canal
- Weave
# 什么是 Calico
Calico 为容器和虚拟机提供了安全的网络连接解决方案,并经过了大规模生产验证(在公有云和跨数千个集群节点中),可与 Kubernetes,OpenShift,Docker,Mesos,DC / OS 和 OpenStack 集成。
Calico 还提供网络安全规则的动态实施。使用 Calico 的简单策略语言,您可以实现对容器,虚拟机工作负载和裸机主机端点之间通信的细粒度控制。
# 下载 Calico 配置文件并修改
wget https://docs.projectcalico.org/v3.8/manifests/calico.yaml
vi calico.yaml
2
修改第 611 行,将 192.168.0.0/16 修改为 10.244.0.0/16,可以通过如下命令快速查找
- 显示行号:
:set number - 查找字符:
/要查找的字符,输入小写n下一个匹配项,输入大写N上一个匹配项

# 安装网络插件 Calico
注意: 截止到文章发表日期 2019 年 07 月 20 日,Calico 官方版本为 3.8
参考官方文档安装:https://docs.projectcalico.org/v3.8/getting-started/kubernetes/
kubectl apply -f calico.yaml
# 输出如下
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
daemonset.extensions/calico-node created
serviceaccount/calico-node created
deployment.extensions/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 验证安装是否成功
- 查看 Calico 网络插件处于 Running 状态即表示安装成功
watch kubectl get pods --all-namespaces
# 输出如下
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-kube-controllers-658558ddf8-9zzjg 1/1 Running 0 90s
kube-system calico-node-9cr5f 1/1 Running 0 91s
kube-system calico-node-n99mz 1/1 Running 0 91s
kube-system calico-node-nl67v 1/1 Running 0 91s
kube-system coredns-bccdc95cf-9s4bm 1/1 Running 0 56m
kube-system coredns-bccdc95cf-s8ggd 1/1 Running 0 56m
kube-system etcd-kubernetes-master 1/1 Running 0 55m
kube-system kube-apiserver-kubernetes-master 1/1 Running 0 55m
kube-system kube-controller-manager-kubernetes-master 1/1 Running 0 55m
kube-system kube-proxy-8s87d 1/1 Running 0 36m
kube-system kube-proxy-cbnlb 1/1 Running 0 36m
kube-system kube-proxy-vwhxj 1/1 Running 0 56m
kube-system kube-scheduler-kubernetes-master 1/1 Running 0 55m
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 查看节点状态处于 Ready 即表示安装成功
kubectl get node
# 输出如下
NAME STATUS ROLES AGE VERSION
kubernetes-master Ready master 57m v1.15.0
kubernetes-node-01 Ready <none> 37m v1.15.0
kubernetes-node-02 Ready <none> 36m v1.15.0
2
3
4
5
6
# Kubernetes 第一个容器
# 检查组件运行状态
kubectl get cs
# 输出如下
NAME STATUS MESSAGE ERROR
# 调度服务,主要作用是将 POD 调度到 Node
scheduler Healthy ok
# 自动化修复服务,主要作用是 Node 宕机后自动修复 Node 回到正常的工作状态
controller-manager Healthy ok
# 服务注册与发现
etcd-0 Healthy {"health":"true"}
2
3
4
5
6
7
8
9
# 检查 Master 状态
kubectl cluster-info
# 输出如下
Kubernetes master is running at https://192.168.141.110:6443
KubeDNS is running at https://192.168.141.110:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
2
3
4
5
# 检查 Nodes 状态
kubectl get nodes
# 输出如下
NAME STATUS ROLES AGE VERSION
kubernetes-master Ready master 72m v1.15.0
kubernetes-node-01 Ready <none> 52m v1.15.0
kubernetes-node-02 Ready <none> 51m v1.15.0
2
3
4
5
6
# 运行第一个容器实例
# 使用 kubectl 命令创建两个监听 80 端口的 Nginx Pod(Kubernetes 运行容器的最小单元)
kubectl run nginx --image=nginx --replicas=2 --port=80
# 输出如下
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created
2
3
4
5
# 查看全部 Pods 的状态
kubectl get pods
# 输出如下
NAME READY STATUS RESTARTS AGE
nginx-7c45b84548-mv8n8 1/1 Running 0 36s
nginx-7c45b84548-vp2x6 1/1 Running 0 36s
2
3
4
5
# 查看已部署的服务
kubectl get deployment
# 输出如下
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 2/2 2 2 70s
2
3
4
# 发布服务
# 使用负载均衡模式发布服务,让用户可以访问
kubectl expose deployment nginx --port=80 --type=LoadBalancer
# 输出如下
service/nginx exposed
2
3
4
# 查看已发布的服务
kubectl get services
# 输出如下
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 81m
nginx LoadBalancer 10.102.37.240 <pending> 80:31406/TCP 115s
2
3
4
5
# 查看服务详情
kubectl describe service nginx
# 输出如下
Name: nginx
Namespace: default
Labels: run=nginx
Annotations: <none>
Selector: run=nginx
Type: LoadBalancer
IP: 10.102.37.240
Port: <unset> 80/TCP
TargetPort: 80/TCP
# 发布到外网的服务端口
NodePort: <unset> 31406/TCP
Endpoints: 192.168.140.66:80,192.168.141.194:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 验证是否成功
通过浏览器访问 Node 服务器,此时 Kubernetes 会以负载均衡的方式访问部署的 Nginx 服务,能够正常看到 Nginx 的欢迎页即表示成功
http://192.168.141.120:31406/
# 停止服务
- 删除已部署的服务
kubectl delete deployment nginx
# 输出如下
deployment.extensions "nginx" deleted
2
3
- 删除已发布的服务
kubectl delete service nginx
# 输出如下
service "nginx" deleted
2
3
# Kubernetes 概念总结
# 什么是 Kubernetes
Kubernetes 是一个开源的 Docker 容器编排系统,它可以调度计算集群的节点,动态管理上面的作业,保证它们按用户期望的状态运行。通过使用「labels」和「pods」的概念,Kubernetes 将应用按逻辑单元进行分组,方便管理和服务发现。

- pods: 是一组紧密关联的容器集合,它们共享 IPC(进程间通信)、Network(网络) 和 UTS namespace(UTS 命名空间是 Linux 命名空间的一个子系统,主要作用是完成对容器 Hostname 和 Domain 的隔离,同时保存内核名称、版本、以及底层体系结构类型等信息),是 Kubernetes 调度的基本单位。
- labels: 键值对(key/value)标签,可以被关联到如 Pod 这样的对象上,主要作用是给用户一个直观的感受,比如这个 Pod 是用来放置数据库的
- GUI: 用户图形界面,可以是 Web 用户界面,比如使用
kubernetes-dashboard组件,用户可以通过 Dashboard 在 Kubernetes 集群中部署容器化的应用,可以查看集群中应用的运行情况,同时也能够基于 Dashboard 创建或修改部署、任务、服务等 Kubernetes 的资源。通过部署向导,用户能够对部署进行扩缩容,进行滚动更新、重启 Pod 和部署新应用。当然,通过 Dashboard 也能够查看 Kubernetes 资源的状态 - kubectl: 用于管理 Kubernetes 集群的命令行工具
- kube-apiserver: 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制
- Kubernetes Master: Kubernetes 集群主节点,主要由
kube-apiserver、kube-scheduler、kube-controller-manager、etcd四个模块组成 - Kubernetes Node: Kubernetes 集群子节点,主要由
kubelet、kube-proxy、runtime三个模块组成 - Image Registry: 镜像仓库,比如:Ducker HUB 或 Docker 私服
# Kubernetes Master

- kube-apiserver: 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制
- kube-scheduler: 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上
- kube-controller-manager: 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等
- etcd: CoreOS 基于 Raft 开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)
# Kubernetes Node

- runtime: 负责镜像管理以及 Pod 和容器的真正运行(CRI,Container Runtime Interface),默认的容器运行时为 Docker,还支持 RKT 容器
- kubelet: 负责维持容器的生命周期,同时也负责 Volume(CVI,Container Volume Interface)和网络(CNI,Container Network Interface)的管理
- kube-proxy: 负责为 Service 提供 cluster 内部的服务发现和负载均衡
# Kubernetes 架构


# Kubernetes 通过资源配置运行容器
# 概述
我们知道通过 run 命令启动容器非常麻烦,Docker 提供了 Compose 为我们解决了这个问题。那 Kubernetes 是如何解决这个问题的呢?其实很简单,使用 kubectl create 命令就可以做到和 Compose 一样的效果了,该命令可以通过配置文件快速创建一个集群资源对象。
# 创建 YAML 配置文件
# 部署 Deployment
创建一个名为 nginx-deployment.yml 的配置文件
# API 版本号
apiVersion: apps/v1
# 类型,如:Pod/ReplicationController/Deployment/Service/Ingress
kind: Deployment
# 元数据
metadata:
# Kind 的名称
name: nginx-app
spec:
selector:
matchLabels:
app: nginx
# 部署的实例数量
replicas: 2
template:
metadata:
labels:
# 容器标签的名字,发布 Service 时,selector 需要和这里对应
app: nginx
spec:
# 配置容器,数组类型,说明可以配置多个容器
containers:
# 容器名称
- name: nginx
# 容器镜像
image: nginx:1.17
# 只有镜像不存在时,才会进行镜像拉取
imagePullPolicy: IfNotPresent
# 暴露端口
ports:
# Pod 端口
- containerPort: 80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 发布 Service
创建一个名为 nginx-service.yml 的配置文件
# API 版本号
apiVersion: v1
# 类型,如:Pod/ReplicationController/Deployment/Service/Ingress
kind: Service
# 元数据
metadata:
# Kind 的名称
name: nginx-http
spec:
# 暴露端口
ports:
## Service 暴露的端口
- port: 80
## Pod 上的端口,这里是将 Service 暴露的端口转发到 Pod 端口上
targetPort: 80
# 类型
type: LoadBalancer
# 标签选择器
selector:
# 需要和上面部署的 Deployment 标签名对应
app: nginx
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 部署
kubectl create -f nginx-service.yml
# 删除
kubectl delete -f nginx-service.yml
2
3
4
# 镜像拉取策略
支持三种 ImagePullPolicy
- Always: 不管镜像是否存在都会进行一次拉取
- Never: 不管镜像是否存在都不会进行拉取
- IfNotPresent: 只有镜像不存在时,才会进行镜像拉取
注意
- 默认为
IfNotPresent,但:latest标签的镜像默认为Always - 拉取镜像时 Docker 会进行校验,如果镜像中的 MD5 码没有变,则不会拉取镜像数据
- 生产环境中应该尽量避免使用
:latest标签,而开发环境中可以借助:latest标签自动拉取最新的镜像
# 验证是否生效
# 查看 Pod 列表
kubectl get pods
# 输出如下
NAME READY STATUS RESTARTS AGE
nginx-app-64bb598779-2pplx 1/1 Running 0 25m
nginx-app-64bb598779-824lc 1/1 Running 0 25m
2
3
4
5
# 查看 Deployment 列表
kubectl get deployment
# 输出如下
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-app 2/2 2 2 25m
2
3
4
# 查看 Service 列表
kubectl get service
# 输出如下
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 20h
nginx-http LoadBalancer 10.98.49.142 <pending> 80:31631/TCP 14m
2
3
4
5
# 查看 Service 详情
kubectl describe service nginx-app
# 输出如下
Name: nginx-http
Namespace: default
Labels: <none>
Annotations: <none>
Selector: name=nginx
Type: LoadBalancer
IP: 10.98.49.142
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 31631/TCP
Endpoints: 10.244.141.205:80,10.244.2.4:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 通过浏览器访问
通过浏览器访问 http://192.168.141.120:31631/ ,出现 Nginx 欢迎页即表示成功
# 集成环境部署
也可以不区分配置文件,一次性部署 Deployment 和 Service,创建一个名为 nginx.yml 的配置文件,配置内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-http
spec:
ports:
- port: 80
targetPort: 80
# 可以指定 NodePort 端口,默认范围是:30000-32767
# nodePort: 30080
type: LoadBalancer
selector:
app: nginx
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 部署
kubectl create -f nginx.yml
# 删除
kubectl delete -f nginx.yml
2
3
4
# 附:扩展阅读
# 修改默认的端口范围
Kubernetes 服务的 NodePort 默认端口范围是 30000-32767,在某些场合下,这个限制不太适用,我们可以自定义它的端口范围,操作步骤如下:
编辑 vi /etc/kubernetes/manifests/kube-apiserver.yaml配置文件,增加配置 --service-node-port-range=2-65535
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
# 在这里增加配置即可
- --service-node-port-range=2-65535
- --advertise-address=192.168.141.150
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
// 以下配置省略...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
使用 docker ps 命令找到 kube-apiserver 容器,再使用 docker restart 即可生效。
# Kubernetes Ingress 简介
# 术语
- 节点: Kubernetes 集群中的服务器
- 集群: Kubernetes 管理的一组服务器集合
- 边界路由器: 为局域网和 Internet 路由数据包的路由器,执行防火墙保护局域网络
- 集群网络: 遵循 Kubernetes 网络模型实现集群内的通信的具体实现,比如 Flannel 和 Calico
- 服务: Kubernetes 的服务 (Service) 是使用标签选择器标识的一组 Pod Service (Deployment)。 除非另有说明,否则服务的虚拟 IP 仅可在集群内部访问
# 内部访问方式 ClusterIP
ClusterIP 服务是 Kubernetes 的默认服务。它给你一个集群内的服务,集群内的其它应用都可以访问该服务。集群外部无法访问它。在某些场景下我们可以使用 Kubernetes 的 Proxy 模式来访问服务,比如调试服务时。

# 三种外部访问方式
# NodePort
NodePort 服务是引导外部流量到你的服务的最原始方式。NodePort,正如这个名字所示,在所有节点(虚拟机)上开放一个特定端口,任何发送到该端口的流量都被转发到对应服务。
NodePort 服务特征如下:
- 每个端口只能是一种服务
- 端口范围只能是 30000-32767(可调)
- 不在 YAML 配置文件中指定则会分配一个默认端口
建议: 不要在生产环境中使用这种方式暴露服务,大多数时候我们应该让 Kubernetes 来选择端口

# LoadBalancer
LoadBalancer 服务是暴露服务到 Internet 的标准方式。所有通往你指定的端口的流量都会被转发到对应的服务。它没有过滤条件,没有路由等。这意味着你几乎可以发送任何种类的流量到该服务,像 HTTP,TCP,UDP,WebSocket,gRPC 或其它任意种类。

# Ingress
Ingress 事实上不是一种服务类型。相反,它处于多个服务的前端,扮演着 “智能路由” 或者集群入口的角色。你可以用 Ingress 来做许多不同的事情,各种不同类型的 Ingress 控制器也有不同的能力。它允许你基于路径或者子域名来路由流量到后端服务。
Ingress 可能是暴露服务的最强大方式,但同时也是最复杂的。Ingress 控制器有各种类型,包括 Google Cloud Load Balancer, Nginx,Contour,Istio,等等。它还有各种插件,比如 cert-manager (它可以为你的服务自动提供 SSL 证书)/
如果你想要使用同一个 IP 暴露多个服务,这些服务都是使用相同的七层协议(典型如 HTTP),你还可以获取各种开箱即用的特性(比如 SSL、认证、路由等等)

# 什么是 Ingress
通常情况下,Service 和 Pod 的 IP 仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到 Service 在 Node 上暴露的 NodePort 上,然后再由 kube-proxy 通过边缘路由器 (edge router) 将其转发给相关的 Pod 或者丢弃。而 Ingress 就是为进入集群的请求提供路由规则的集合
Ingress 可以给 Service 提供集群外部访问的 URL、负载均衡、SSL 终止、HTTP 路由等。为了配置这些 Ingress 规则,集群管理员需要部署一个 Ingress Controller,它监听 Ingress 和 Service 的变化,并根据规则配置负载均衡并提供访问入口。
# Nginx 虚拟主机
# 什么是 Nginx
Nginx 是一款高性能的 HTTP 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。由俄罗斯的程序设计师 Igor Sysoev 所开发,官方测试 Nginx 能够支支撑 5 万并发链接,并且 CPU、内存等资源消耗却非常低,运行非常稳定。
# Nginx 的应用场景
- HTTP 服务器:Nginx 是一个 HTTP 服务可以独立提供 HTTP 服务。可以做网页静态服务器。
- 虚拟主机:可以实现在一台服务器虚拟出多个网站。例如个人网站使用的虚拟主机。
- 反向代理,负载均衡:当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用 Nginx 做反向代理。并且多台服务器可以平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置的情况。
# Nginx 虚拟主机
# 什么是虚拟主机
虚拟主机是一种特殊的软硬件技术,它可以将网络上的每一台计算机分成多个虚拟主机,每个虚拟主机可以独立对外提供 www 服务,这样就可以实现一台主机对外提供多个 web 服务,每个虚拟主机之间是独立的,互不影响的。
通过 Nginx 可以实现虚拟主机的配置,Nginx 支持三种类型的虚拟主机配置
- 基于 IP 的虚拟主机
- 基于域名的虚拟主机
- 基于端口的虚拟主机
# Nginx 配置文件的结构
# ...
events {
# ...
}
http {
# ...
server{
# ...
}
# ...
server{
# ...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意: 每个 server 就是一个虚拟主机
# 基于 Docker 部署 Nginx
version: '3.1'
services:
nginx:
restart: always
image: nginx
container_name: nginx
ports:
- 80:80
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./html:/usr/share/nginx/html
2
3
4
5
6
7
8
9
10
11
# 基于端口的虚拟主机配置
# 需求
- Nginx 对外提供 80 和 8080 两个端口监听服务
- 请求 80 端口则请求 html80 目录下的 html
- 请求 8080 端口则请求 html8080 目录下的 html
# 操作流程
- 创建目录及文件,在
/usr/local/docker/nginx/html目录下创建html80和html8080两个目录,并分别创建两个index.html文件 - 配置虚拟主机,创建并修改
/usr/local/docker/nginx/conf目录下的nginx.conf
# 启动进程,通常设置成和 CPU 的数量相等
worker_processes 1;
events {
# epoll 是多路复用 IO(I/O Multiplexing) 中的一种方式
# 但是仅用于 linux2.6 以上内核,可以大大提高 nginx 的性能
use epoll;
# 单个后台 worker process 进程的最大并发链接数
worker_connections 1024;
}
http {
# 设定 mime 类型,类型由 mime.type 文件定义
include mime.types;
default_type application/octet-stream;
# sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,对于普通应用,
# 必须设为 on,如果用来进行下载等应用磁盘 IO 重负载应用,可设置为 off,以平衡磁盘与网络 I/O 处理速度,降低系统的 uptime.
sendfile on;
# 连接超时时间
keepalive_timeout 65;
# 设定请求缓冲
client_header_buffer_size 2k;
# 配置虚拟主机 192.168.141.121
server {
# 监听的 IP 和端口,配置 192.168.141.121:80
listen 80;
# 虚拟主机名称这里配置 IP 地址
server_name 192.168.141.121;
# 所有的请求都以 / 开始,所有的请求都可以匹配此 location
location / {
# 使用 root 指令指定虚拟主机目录即网页存放目录
# 比如访问 http://ip/index.html 将找到 /usr/local/docker/nginx/html/html80/index.html
# 比如访问 http://ip/item/index.html 将找到 /usr/local/docker/nginx/html/html80/item/index.html
root /usr/share/nginx/html/html80;
# 指定欢迎页面,按从左到右顺序查找
index index.html index.htm;
}
}
# 配置虚拟主机 192.168.141.121
server {
listen 8080;
server_name 192.168.141.121;
location / {
root /usr/share/nginx/html/html8080;
index index.html index.htm;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 基于域名的虚拟主机配置
# 需求
- 两个域名指向同一台 Nginx 服务器,用户访问不同的域名显示不同的网页内容
- 两个域名是 ingress.funtl.com 和 service.funtl.com
- Nginx 服务器使用虚拟机 192.168.141.121
# 操作流程
- 配置 Hosts,通过 host 文件指定 service1.funtl.com 和 service2.funtl.com 对应 192.168.141.121 虚拟机,修改 window 的 hosts 文件:(C:\Windows\System32\drivers\etc)
- 创建目录及文件,在
/usr/local/docker/nginx/html目录下创建service1和service1两个目录,并分别创建两个 index.html 文件 - 配置虚拟主机,修改
/usr/local/docker/nginx/conf目录下的nginx.conf
user nginx;
worker_processes 1;
events {
use epoll;
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name service1.funtl.com;
location / {
root /usr/share/nginx/html/service1;
index index.html index.htm;
}
}
server {
listen 80;
server_name service2.funtl.com;
location / {
root /usr/share/nginx/html/service2;
index index.html index.htm;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Nginx 反向代理
# 什么是代理服务器
代理服务器,客户机在发送请求时,不会直接发送给目的主机,而是先发送给代理服务器,代理服务接受客户机请求之后,再向主机发出,并接收目的主机返回的数据,存放在代理服务器的硬盘中,再发送给客户机。

# 为什么要使用代理服务器
- 提高访问速度: 由于目标主机返回的数据会存放在代理服务器的硬盘中,因此下一次客户再访问相同的站点数据时,会直接从代理服务器的硬盘中读取,起到了缓存的作用,尤其对于热门站点能明显提高请求速度。
- 防火墙作用: 由于所有的客户机请求都必须通过代理服务器访问远程站点,因此可在代理服务器上设限,过滤某些不安全信息。
- 通过代理服务器访问不能访问的目标站点: 互联网上有许多开放的代理服务器,客户机在访问受限时,可通过不受限的代理服务器访问目标站点,通俗说,我们使用的翻墙浏览器就是利用了代理服务器,虽然不能出国,但也可直接访问外网。
# 什么是正向代理
正向代理,架设在客户机与目标主机之间,只用于代理内部网络对 Internet 的连接请求,客户机必须指定代理服务器,并将本来要直接发送到 Web 服务器上的 HTTP 请求发送到代理服务器中。

# 什么是反向代理
反向代理服务器架设在服务器端,通过缓冲经常被请求的页面来缓解服务器的工作量,将客户机请求转发给内部网络上的目标服务器;并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器与目标主机一起对外表现为一个服务器。

# 反向代理有哪些主要应用
现在许多大型 web 网站都用到反向代理。除了可以防止外网对内网服务器的恶性攻击、缓存以减少服务器的压力和访问安全控制之外,还可以进行负载均衡,将用户请求分配给多个服务器。
# 使用 Nginx 反向代理 Tomcat
# 需求
- 两个 Tomcat 服务通过 Nginx 反向代理
- Nginx 服务器:192.168.141.121:80
- Tomcat1 服务器:192.168.141.121:8081
- Tomcat2 服务器:192.168.141.121:8082
# 启动 Tomcat 容器
启动两个 Tomcat 容器,映射端口为 8081 和 8082,docker-compose.yml 如下:
version: '3.1'
services:
tomcat1:
image: tomcat
container_name: tomcat1
ports:
- 8081:8080
tomcat2:
image: tomcat
container_name: tomcat2
ports:
- 8082:8080
2
3
4
5
6
7
8
9
10
11
12
# 配置 Nginx 反向代理
修改 /usr/local/docker/nginx/conf 目录下的 nginx.conf 配置文件:
user nginx;
worker_processes 1;
events {
use epoll;
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 配置一个代理即 tomcat1 服务器
upstream tomcatServer1 {
server 192.168.141.121:8081;
}
# 配置一个代理即 tomcat2 服务器
upstream tomcatServer2 {
server 192.168.141.121:8082;
}
# 配置一个虚拟主机
server {
listen 80;
server_name service1.funtl.com;
location / {
# 域名 service1.funtl.com 的请求全部转发到 tomcatServer1 即 tomcat1 服务上
proxy_pass http://tomcatServer1;
# 欢迎页面,按照从左到右的顺序查找页面
index index.jsp index.html index.htm;
}
}
server {
listen 80;
server_name service2.funtl.com;
location / {
# 域名 service2.funtl.com 的请求全部转发到 tomcatServer2 即 tomcat2 服务上
proxy_pass http://tomcatServer2;
index index.jsp index.html index.htm;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
注意: 新版 Nginx 的
MARKDOWN_HASHbc3b0556316b0ba241ae6bb86b76e8a2MARKDOWN*HASH*配置中的名称不可以有下划线(""),否则会报400错误
# Nginx 负载均衡
# 什么是负载均衡
负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡,英文名称为 Load Balance,其意思就是分摊到多个操作单元上进行执行,例如 Web 服务器、FTP 服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。
# Nginx 实现负载均衡
- Nginx 作为负载均衡服务器,用户请求先到达 Nginx,再由 Nginx 根据负载配置将请求转发至 Tomcat 服务器
- Nginx 负载均衡服务器:192.168.141.121:80
- Tomcat1 服务器:192.168.141.121:8081
- Tomcat2 服务器:192.168.141.121:8082
- 修改
/usr/local/docker/nginx/conf目录下的nginx.conf配置文件:
user nginx;
worker_processes 1;
events {
use epoll;
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream myapp1 {
server 192.168.141.121:8081 weight=10;
server 192.168.141.121:8082 weight=10;
}
server {
listen 80;
server_name nginx.funtl.com;
location / {
proxy_pass http://myapp1;
index index.jsp index.html index.htm;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 相关配置说明
# 定义负载均衡设备的 Ip及设备状态
upstream myServer {
server 127.0.0.1:9090 down;
server 127.0.0.1:8080 weight=2;
server 127.0.0.1:6060;
server 127.0.0.1:7070 backup;
}
2
3
4
5
6
7
在需要使用负载的 Server 节点下添加
proxy_pass http://myServer;
upstream:每个设备的状态:down:表示当前的server暂时不参与负载weight:默认为 1weight越大,负载的权重就越大。max_fails:允许请求失败的次数默认为 1 当超过最大次数时,返回proxy_next_upstream模块定义的错误fail_timeout:max_fails次失败后,暂停的时间。backup:其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻
# Nginx Ingress Controller
# 概述
本次实践的主要目的就是将入口统一,不再通过 LoadBalancer 等方式将端口暴露出来,而是使用 Ingress 提供的反向代理负载均衡功能作为我们的唯一入口。通过以下步骤操作仔细体会。
注意: 下面包含资源配置的步骤都是自行创建 YAML 配置文件通过
kubectl create -f或kubectl apply -f部署,kubectl delete -f删除
# 安装 Ingress
Ingress Controller 有许多种,我们选择最熟悉的 Nginx 来处理请求,其它可以参考 官方文档
- 下载 Nginx Ingress Controller 配置文件
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
- 修改配置文件,找到配置如下位置 (搜索
serviceAccountName) 在下面增加一句hostNetwork: true
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
# 可以部署多个实例
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
serviceAccountName: nginx-ingress-serviceaccount
# 增加 hostNetwork: true,意思是开启主机网络模式,暴露 Nginx 服务端口 80
hostNetwork: true
containers:
- name: nginx-ingress-controller
# 使用 Azure 中国镜像
image: quay.azk8s.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
// 以下代码省略...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 通过命令
kubectl apply -f mandatory.yaml部署 - 通过命令
kubectl get pods -n ingress-nginx -o wide查看
# 部署 Ingress
- 创建一个名为
ingress.yml的资源配置文件
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: nginx-web
annotations:
# 指定 Ingress Controller 的类型
kubernetes.io/ingress.class: "nginx"
# 指定我们的 rules 的 path 可以使用正则表达式
nginx.ingress.kubernetes.io/use-regex: "true"
# 连接超时时间,默认为 5s
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
# 后端服务器回转数据超时时间,默认为 60s
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
# 后端服务器响应超时时间,默认为 60s
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
# 客户端上传文件,最大大小,默认为 20m
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
# URL 重写
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
# 路由规则
rules:
# 主机名,只能是域名,修改为你自己的
- host: k8s.funtl.com
http:
paths:
- path:
backend:
# 后台部署的 Service Name
serviceName: tomcat-http
# 后台部署的 Service Port
servicePort: 8080
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- 通过命令
kubectl apply -f ingress.yml部署 - 通过命令
kubectl get ingress查看
# 部署 Tomcat
部署 Tomcat 但仅允许在内网访问,我们要通过 Ingress 提供的反向代理功能路由到 Tomcat 之上,创建一个名为 tomcat.yml 资源配置文件
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tomcat-app
spec:
replicas: 2
template:
metadata:
labels:
name: tomcat
spec:
containers:
- name: tomcat
image: tomcat:8.5.43
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-http
spec:
ports:
- port: 8080
targetPort: 8080
# ClusterIP, NodePort, LoadBalancer
type: ClusterIP
selector:
name: tomcat
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- 通过命令
kubectl apply -f tomcat.yml部署
# 验证是否成功
# 查看 Tomcat
kubectl get deployment
# 输出如下
NAME READY UP-TO-DATE AVAILABLE AGE
tomcat-app 2/2 2 2 88m
2
3
4
kubectl get service
# 输出如下
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d5h
tomcat-http ClusterIP 10.97.222.179 <none> 8080/TCP 89m
2
3
4
5
# 查看 Ingress
kubectl get pods -n ingress-nginx -o wide
# 输出如下,注意下面的 IP 地址,就是我们实际访问地址
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-ingress-controller-76f9fddcf8-vzkm5 1/1 Running 0 61m 192.168.141.121 kubernetes-node-02 <none> <none>
2
3
4
kubectl get ingress
# 输出如下
NAME HOSTS ADDRESS PORTS AGE
nginx-web k8s.funtl.com 80 61m
2
3
4
# 测试访问
成功代理到 Tomcat 即表示成功
# 不设置 Hosts 的方式请求地址,下面的 IP 和 HOST 均在上面有配置
curl -v http://192.168.12.120 -H 'host: k8s.smxy.com'
2
# Kubernetes 准备数据卷
# 概述
在 Docker 中就有数据卷的概念,当容器删除时,数据也一起会被删除,想要持久化使用数据,需要把主机上的目录挂载到 Docker 中去,在 K8S 中,数据卷是通过 Pod 实现持久化的,如果 Pod 删除,数据卷也会一起删除,k8s 的数据卷是 docker 数据卷的扩展,K8S 适配各种存储系统,包括本地存储 EmptyDir,HostPath, 网络存储(NFS,GlusterFS,PV/PVC)等。
我们以部署 MySQL8 为例,采用 NFS + PV/PVC 网络存储方案实现我们的 Kubernetes 数据持久化。
# 什么是 NFS
NFS 是 Network File System 的简写,即网络文件系统,NFS 是 FreeBSD 支持的文件系统中的一种。NFS 基于 RPC (Remote Procedure Call) 远程过程调用实现,其允许一个系统在网络上与它人共享目录和文件。通过使用 NFS,用户和程序就可以像访问本地文件一样访问远端系统上的文件。NFS 是一个非常稳定的,可移植的网络文件系统。具备可扩展和高性能等特性,达到了企业级应用质量标准。由于网络速度的增加和延迟的降低,NFS 系统一直是通过网络提供文件系统服务的有竞争力的选择 。
# NFS 原理
NFS 使用 RPC (Remote Procedure Call) 的机制进行实现,RPC 使得客户端可以调用服务端的函数。同时,由于有 VFS 的存在,客户端可以像使用其它普通文件系统一样使用 NFS 文件系统。经由操作系统的内核,将 NFS 文件系统的调用请求通过 TCP/IP 发送至服务端的 NFS 服务。NFS 服务器执行相关的操作,并将操作结果返回给客户端。

# NFS 服务主要进程
- rpc.nfsd:最主要的 NFS 进程,管理客户端是否可登录
- rpc.mountd:挂载和卸载 NFS 文件系统,包括权限管理
- rpc.lockd:非必要,管理文件锁,避免同时写出错
- rpc.statd:非必要,检查文件一致性,可修复文件
# NFS 的关键工具
- 主要配置文件:
/etc/exports - NFS 文件系统维护命令:
/usr/bin/exportfs - 共享资源的日志文件:
/var/lib/nfs/*tab - 客户端查询共享资源命令:
/usr/sbin/showmount - 端口配置:
/etc/sysconfig/nfs
# NFS 服务端配置
在 NFS 服务器端的主要配置文件为 /etc/exports 时,通过此配置文件可以设置共享文件目录。每条配置记录由 NFS 共享目录、NFS 客户端地址和参数这 3 部分组成,格式如下:
[NFS 共享目录] [NFS 客户端地址 1 (参数 1, 参数 2, 参数 3……)] [客户端地址 2 (参数 1, 参数 2, 参数 3……)]
- NFS 共享目录:服务器上共享出去的文件目录
- NFS 客户端地址:允许其访问的 NFS 服务器的客户端地址,可以是客户端 IP 地址,也可以是一个网段 (192.168.141.0/24)
- 访问参数:括号中逗号分隔项,主要是一些权限选项
# 访问权限参数
| 序号 | 选项 | 描述 |
|---|---|---|
| 1 | ro | 客户端对于共享文件目录为只读权限。默认 |
| 2 | rw | 客户端对于共享文件目录具有读写权限 |
# 用户映射参数
| 序号 | 选项 | 描述 |
|---|---|---|
| 1 | root_squash | 使客户端使用 root 账户访冋时,服务器映射为服务器本地的匿名账号 |
| 2 | no_root_squash | 客户端连接服务端时如果使用的是 root,那么也拥有对服务端分享的目录的 root 权限 |
| 3 | all_squash | 将所有客户端用户请求映射到匿名用户或用户组(nfsnobody) |
| 4 | no_all_squash | 与上相反。默认 |
| 5 | anonuid=xxx | 将远程访问的所有用户都映射为匿名用户,并指定该用户为本地用户(UID=xxx) |
| 6 | anongid=xxx | 将远程访问的所有用户组都映射为匿名用户组账户,并指定该匿名用户组账户为本地用户组账户(GUI=xxx) |
# 其它配置参数
| 序号 | 选项 | 描述 |
|---|---|---|
| 1 | sync | 同步写操作,数据写入存储设备后返回成功信息。默认 |
| 2 | async | 异步写提作,数据在未完全写入存储设备前就返回成功信息,实际还在内存, |
| 3 | wdelay | 延迟写入选项,将多个写提请求合并后写入硬盘,减少 I/O 次数, NFS 非正常关闭数据可能丢失。默认 |
| 4 | no_wdelay | 与上相反,不与 async 同时生效,如果 NFS 服务器主要收到小且不相关的请求,该选项实际会降低性能 |
| 5 | subtree | 若输出目录是一个子目录,则 NFS 服务器将检查其父目录的权限。默认 |
| 6 | no_subtree | 即使输出目录是一个子目录, NFS 服务器也不检查其父目录的权限,这样可以提高效率 |
| 7 | secure | 限制客户端只能从小于 1024 的 TCP/IP 端口连接 NFS 服务器。默认 |
| 8 | insecure | 允许客户端从大于 1024 的 TCP/IP 端口连接服务器 |
# 安装 NFS 服务端
由于 NFS 是一套分布式文件系统,我们再创建一台独立的虚拟机作为我们 NFS 服务端,配置如下
| 主机名 | IP | 系统 | CPU/内存 | 磁盘 |
|---|---|---|---|---|
| kubernetes-volumes | 192.168.141.130 | Ubuntu Server 18.04 | 2 核 2G | 20G |
- 创建一个目录作为共享文件目录
mkdir -p /usr/local/kubernetes/volumes
- 给目录增加读写权限
chmod a+rw /usr/local/kubernetes/volumes
- 安装 NFS 服务端
apt-get update
apt-get install -y nfs-kernel-server
2
- 配置 NFS 服务目录,打开文件
vi /etc/exports/usr/local/kubernetes/volumes: 作为服务目录向客户端开放- ***:**表示任何 IP 都可以访问
- rw: 读写权限
- sync: 同步权限
- no_subtree_check: 表示如果输出目录是一个子目录,NFS 服务器不检查其父目录的权限
- no_root_squash: 客户端连接服务端时如果使用的是 root,那么也拥有对服务端分享的目录的 root 权限
/usr/local/kubernetes/volumes *(rw,sync,no_subtree_check,no_root_squash)
- 重启服务,使配置生效
/etc/init.d/nfs-kernel-server restart
# 安装 NFS 客户端
安装客户端的目的是验证是否可以上传文件到服务端,安装命令如下
apt-get install -y nfs-common
- 创建 NFS 客户端挂载目录
mkdir -p /usr/local/kubernetes/volumes-mount
- 将 NFS 服务器的
/usr/local/kubernetes/volumes目录挂载到 NFS 客户端的/usr/local/kubernetes/volumes-mount目录
mount 192.168.141.130:/usr/local/kubernetes/volumes /usr/local/kubernetes/volumes-mount
- 使用
df命令查看挂载信息
df
# 输出如下
Filesystem 1K-blocks Used Available Use% Mounted on
udev 977544 0 977544 0% /dev
tmpfs 201732 1232 200500 1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv 19475088 4780912 13681852 26% /
tmpfs 1008648 0 1008648 0% /dev/shm
tmpfs 5120 0 5120 0% /run/lock
tmpfs 1008648 0 1008648 0% /sys/fs/cgroup
/dev/loop0 90624 90624 0 100% /snap/core/7270
/dev/loop1 93184 93184 0 100% /snap/core/6350
/dev/sda2 999320 77944 852564 9% /boot
tmpfs 201728 0 201728 0% /run/user/0
# 有此输出表示挂载成功
192.168.141.130:/usr/local/kubernetes/volumes 19475200 4780800 13681920 26% /usr/local/kubernetes/volumes-mount
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 验证 NFS 服务
- 测试文件上传
ip addr > /usr/local/kubernetes/volumes-mount/test.txt
- 查看
/usr/local/kubernetes/volumes目录下是否有test.txt文件,有则表示成功
# 取消 NFS 客户端挂载
注意: 不要直接在挂载目录下执行,否则会报错
umount /usr/local/kubernetes/volumes-mount
# Kubernetes 使用数据卷
# 概述
存储管理与计算管理是两个不同的问题。Persistent Volume 子系统,对存储的供应和使用做了抽象,以 API 形式提供给管理员和用户使用。要完成这一任务,我们引入了两个新的 API 资源:Persistent Volume(持久卷) 和 Persistent Volume Claim(持久卷消费者)。
Persistent Volume(PV)是集群之中的一块网络存储。跟 Node 一样,也是集群的资源。PV 跟 Volume (卷) 类似,不过会有独立于 Pod 的生命周期。这一 API 对象包含了存储的实现细节,例如 NFS、iSCSI 或者其他的云提供商的存储系统。Persistent Volume Claim (PVC) 是用户的一个请求。跟 Pod 类似,Pod 消费 Node 的资源,PVC 消费 PV 的资源。Pod 能够申请特定的资源(CPU 和内存);Claim 能够请求特定的尺寸和访问模式(例如可以加载一个读写,以及多个只读实例)
# PV 与 PVC
PV 是集群的资源。PVC 是对这一资源的请求,也是对资源的所有权的检验。PV 和 PVC 之间的互动遵循如下的生命周期。
- 供应: 集群管理员会创建一系列的 PV。这些 PV 包含了为集群用户提供的真实存储资源,它们可利用 Kubernetes API 来消费。
- 绑定: 用户创建一个包含了容量和访问模式的持久卷申请。Master 会监听 PVC 的产生,并尝试根据请求内容查找匹配的 PV,并把 PV 和 PVC 进行绑定。用户能够获取满足需要的资源,并且在使用过程中可能超出请求数量。如果找不到合适的卷,这一申请就会持续处于非绑定状态,一直到出现合适的 PV。例如一个集群准备了很多的 50G 大小的持久卷,(虽然总量足够)也是无法响应 100G 的申请的,除非把 100G 的 PV 加入集群。
- 使用: Pod 把申请作为卷来使用。集群会通过 PVC 查找绑定的 PV,并 Mount 给 Pod。对于支持多种访问方式的卷,用户在使用 PVC 作为卷的时候,可以指定需要的访问方式。一旦用户拥有了一个已经绑定的 PVC,被绑定的 PV 就归该用户所有了。用户的 Pods 能够通过在 Pod 的卷中包含的 PVC 来访问他们占有的 PV。
- 释放: 当用户完成对卷的使用时,就可以利用 API 删除 PVC 对象了,而且他还可以重新申请。删除 PVC 后,对应的卷被视为 “被释放”,但是这时还不能给其他的 PVC 使用。之前的 PVC 数据还保存在卷中,要根据策略来进行后续处理。
- 回收: PV 的回收策略向集群阐述了在 PVC 释放卷的时候,应如何进行后续工作。目前可以采用三种策略:保留,回收或者删除。保留策略允许重新申请这一资源。在持久卷能够支持的情况下,删除策略会同时删除持久卷以及 AWS EBS/GCE PD 或者 Cinder 卷中的存储内容。如果插件能够支持,回收策略会执行基础的擦除操作(
rm -rf /thevolume/*),这一卷就能被重新申请了。
# 定义 PV
# 持久卷插件
持久卷是以插件方式实现的,目前支持的插件如下:
- GCEPersistentDisk
- AWSElasticBlockStore
- NFS(我们采用的是该方案)
- iSCSI
- RBD (Ceph Block Device)
- Glusterfs
- HostPath (单节点测试使用)
- 本地持久卷
# YAML 配置
创建一个名为 nfs-pv-mysql.yml 的配置文件
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-mysql
spec:
# 设置容量
capacity:
storage: 5Gi
# 访问模式
accessModes:
# 该卷能够以读写模式被多个节点同时加载
- ReadWriteMany
# 回收策略,这里是基础擦除 `rm-rf/thevolume/*`
persistentVolumeReclaimPolicy: Recycle
nfs:
# NFS 服务端配置的路径
path: "/usr/local/kubernetes/volumes"
# NFS 服务端地址
server: 192.168.12.130
readOnly: false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 部署
kubectl create -f nfs-pv-mysql.yml
# 删除
kubectl delete -f nfs-pv-mysql.yml
# 查看
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv-mysql 5Gi RWX Recycle Available 29m
2
3
4
5
6
7
8
# 配置说明
# Capacity(容量)
一般来说,PV 会指定存储容量。这里需要使用 PV 的 capcity 属性。目前存储大小是唯一一个能够被申请的指标,今后会加入更多属性,例如 IOPS,吞吐能力等。
# AccessModes(访问模式)
只要资源提供者支持,持久卷能够被用任何方式加载到主机上。每种存储都会有不同的能力,每个 PV 的访问模式也会被设置成为该卷所支持的特定模式。例如 NFS 能够支持多个读写客户端,但是某个 NFS PV 可能会在服务器上以只读方式使用。每个 PV 都有自己的一系列的访问模式,这些访问模式取决于 PV 的能力。访问模式的可选范围如下:
- ReadWriteOnce: 该卷能够以读写模式被加载到一个节点上
- ReadOnlyMany: 该卷能够以只读模式加载到多个节点上
- ReadWriteMany: 该卷能够以读写模式被多个节点同时加载
在 CLI 下,访问模式缩写为:
- RWO: ReadWriteOnce
- ROX: ReadOnlyMany
- RWX: ReadWriteMany
另外,一个卷不论支持多少种访问模式,同时只能以一种访问模式加载。例如一个 GCE Persistent Disk 既能支持 ReadWriteOnce,也能支持 ReadOnlyMany。
# RecyclingPolicy(回收策略)
当前的回收策略可选值包括:
- Retain: 人工重新申请
- Recycle: 基础擦除(
rm-rf/thevolume/*) - Delete: 相关的存储资产例如 AWS EBS,GCE PD 或者 OpenStack Cinder 卷一并删除
目前,只有 NFS 和 HostPath 支持 Recycle 策略,AWS EBS、GCE PD 以及 Cinder 卷支持 Delete 策略。
# 阶段(Phase)
一个卷会处于如下阶段之一:
- Available: 可用资源,尚未被绑定到 PVC 上
- Bound: 该卷已经被绑定
- Released: PVC 已经被删除,但该资源尚未被集群回收
- Failed: 该卷的自动回收过程失败
# 定义 PVC
创建一个名为 nfs-pvc-mysql-myshop.yml 的配置文件
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-mysql-myshop
spec:
accessModes:
# 需要使用和 PV 一致的访问模式
- ReadWriteMany
# 按需分配资源
resources:
requests:
storage: 1Gi
2
3
4
5
6
7
8
9
10
11
12
# 部署
kubectl create -f nfs-pvc-mysql-myshop.yml
# 删除
kubectl delete -f nfs-pvc-mysql-myshop.yml
# 查看
kubectl get pvc
2
3
4
5
6
# 部署 MySQL8
注意: 要确保每台 Node 都安装了 NFS 客户端,
apt-get install -y nfs-common
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mysql-myshop
spec:
replicas: 1
template:
metadata:
labels:
name: mysql-myshop
spec:
containers:
- name: mysql-myshop
image: mysql:8.0.16
# 只有镜像不存在时,才会进行镜像拉取
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3306
# 同 Docker 配置中的 environment
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
# 容器中的挂载目录
volumeMounts:
- name: nfs-vol-myshop
mountPath: /var/lib/mysql
volumes:
# 挂载到数据卷
- name: nfs-vol-myshop
persistentVolumeClaim:
claimName: nfs-pvc-mysql-myshop
---
apiVersion: v1
kind: Service
metadata:
name: mysql-myshop
spec:
ports:
- port: 3306
targetPort: 3306
type: LoadBalancer
selector:
name: mysql-myshop
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 测试运行
部署成功后可以使用 kubectl get service 查看我们 MySQL 的运行端口,再使用连接工具连接会报如下错误

意思为无法使用密码的方式登录,在 Docker 部署时我们可以在 YAML 中配置相关参数解决这个问题;下一节我们讲解在 Kubernetes 中采用 ConfigMap 的方式配置 MySQL
# Kubernetes ConfigMap
# 概述
ConfigMap 是用来存储配置文件的 Kubernetes 资源对象,所有的配置内容都存储在 etcd 中。它可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制对象。ConfigMap API 资源提供了将配置数据注入容器的方式,同时保证该机制对容器来说是透明的。配置应该从 Image 内容中解耦,以此来保持容器化应用程序的可移植性。
# 使用 ConfigMap 配置 MySQL
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-myshop-config
data:
# 这里是键值对数据
mysqld.cnf: |
[client]
port=3306
[mysql]
no-auto-rehash
[mysqld]
skip-host-cache
skip-name-resolve
default-authentication-plugin=mysql_native_password
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
explicit_defaults_for_timestamp=true
lower_case_table_names=1
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mysql-myshop
spec:
replicas: 1
template:
metadata:
labels:
name: mysql-myshop
spec:
containers:
- name: mysql-myshop
image: mysql:8.0.16
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
volumeMounts:
# 以数据卷的形式挂载 MySQL 配置文件目录
- name: cm-vol-myshop
mountPath: /etc/mysql/conf.d
- name: nfs-vol-myshop
mountPath: /var/lib/mysql
volumes:
# 将 ConfigMap 中的内容以文件形式挂载进数据卷
- name: cm-vol-myshop
configMap:
name: mysql-myshop-config
items:
# ConfigMap 中的 Key
- key: mysqld.cnf
# ConfigMap Key 匹配的 Value 写入名为 mysqld.cnf 的文件中
path: mysqld.cnf
- name: nfs-vol-myshop
persistentVolumeClaim:
claimName: nfs-pvc-mysql-myshop
---
apiVersion: v1
kind: Service
metadata:
name: mysql-myshop
spec:
ports:
- port: 3306
targetPort: 3306
type: LoadBalancer
selector:
name: mysql-myshop
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 查看 ConfigMap
kubectl get cm
kubectl describe cm <ConfigMap Name>
2
3
# Kubernetes Dashboard
# 概述
Kubernetes Dashboard 是 Kubernetes 集群的 Web UI,用于管理集群。
# 安装
GitHub 地址:Kubernetes Dashboard
下载配置文件
# k8s1.15
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta2/aio/deploy/recommended.yaml
2
# k8s1.16
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta5/aio/deploy/recommended.yaml
2
修改配置如下
# 省略部分代码...
# ------------------- Dashboard Service ------------------- #
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
# 修改类型为 NodePort 访问
type: NodePort
ports:
- port: 443
targetPort: 8443
# 设置端口号为 30001
nodePort: 30001
selector:
k8s-app: kubernetes-dashboard
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
部署到集群
# 部署
kubectl create -f recommended.yaml
# 查看
kubectl -n kubernetes-dashboard get pods
kubectl -n kubernetes-dashboard get service kubernetes-dashboard
kubectl -n kubernetes-dashboard describe service kubernetes-dashboard
2
3
4
5
6
# 访问
需要使用 NodeIP:30001 访问 Dashboard,因为证书原因除火狐浏览器外其它浏览器无法直接打开页面
Chrome 浏览器显示如下

Firefox 浏览器显示如下

点击 接受风险并继续 即可显示欢迎界面

# 登录
我们采用 Token 方式登录
- 创建登录账号,创建一个名为
dashboard-adminuser.yaml的配置文件
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
kubectl create -f dashboard-adminuser.yaml
- 打印 Token 信息
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')
# 输出如下
Name: admin-user-token-2v8vd
Namespace: kubernetes-dashboard
Labels: <none>
Annotations: kubernetes.io/service-account.name: admin-user
kubernetes.io/service-account.uid: f6f23bc9-3f34-4452-9977-8915e77fbef3
Type: kubernetes.io/service-account-token
Data
====
namespace: 20 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLTJ2OHZkIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmNmYyM2JjOS0zZjM0LTQ0NTItOTk3Ny04OTE1ZTc3ZmJlZjMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.d7TVh5L9OKBLY62FXF8ZDjp7dZMH6hmOgd0QKCwPaNEGL_pzJrz1j5JC0TlSaQgFXJKVhUq3WzBsKC2yAsgOc53AKNfJbv0UWKVk5sLXwi79rZiaUdB6cGHOVxiMxUHsluhDAs7DoALyOT3svY5JSh_f5f5h92ZUxBTx5CeFHr47B_MnjG0kgeRR7p8i2vV1OLnW0JQDg-eI0usfFYIzjBy3z1NiVjJJ8ON_ygUNapSXQLuLanjv0Vei_XVTaxfqL9-8AabSm_VDo-mTkqDJWK-utBvQ1AajOWs9fbz8ey6GiC33KevaxxPjNvKfIJI2h0FvDUffy6qysT-IuN2ZEA
ca.crt: 1025 bytes
2
3
4
5
6
7
8
9
10
11
12
13
- 将 Token 输入浏览器,成功登陆后效果如下

# 使用 Kuboard 替代 Kubernetes Dashboard
# 概述
Kubernetes 已然是当下容器编排领域事实上的标准,各大云服务商都急于推出 Kubernetes 服务,互联网公司也纷纷跟进,将自己的应用容器化,并使用 Kubernetes 编排,在 Kubernetes 图形化工具方面,我们已经获得了极大的可选择空间:
- 各云服务商自己推出的 Kubernetes 服务所搭载的管理控制台,例如 阿里云的 Kubernetes 服务,青云推出的 KubeSphere,其他云服务商的 CaaS 类服务
- Kubernetes 官方的图形管理界面 Kubernetes Dashboard
- 面向企业私有化部署的 Rancher
# 什么是 Kuboard
Kuboard 类似于 Kubernetes Dashboard,是一款轻量级产品,按照 Kuboard 的文档,您可以很快地搭建一套学习 Kubernetes 用的集群环境,您也可以在自己已经有的集群上轻易地完成 Kuboard 的安装
# 安装 Kuboard
如果您已经有了 Kubernetes 集群,只需要一行命令即可安装 Kuboard:
kubectl apply -f https://kuboard.cn/install-script/kuboard.yaml
然后访问您集群中任意节点的 32567 端口(http://any-of-your-node-ip:32567) ,即可打开 Kuboard 界面,比如我的 Node 节点 IP 为:http://192.168.12.110:32567
# 获取 Token
此 Token 拥有 ClusterAdmin 的权限,可以执行所有操作
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep kuboard-user | awk '{print $1}')
# 输出如下
Name: kuboard-user-token-dsccx
Namespace: kube-system
Labels: <none>
Annotations: kubernetes.io/service-account.name: kuboard-user
kubernetes.io/service-account.uid: 08aad1b1-3d1b-4b6f-a379-3e320620a09f
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1025 bytes
namespace: 11 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJrdWJvYXJkLXVzZXItdG9rZW4tZHNjY3giLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoia3Vib2FyZC11c2VyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMDhhYWQxYjEtM2QxYi00YjZmLWEzNzktM2UzMjA2MjBhMDlmIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmt1Ym9hcmQtdXNlciJ9.rndy-u-CxDF_yfaubq2HmIb7o04uLI3z7soqTf581VPLFo31dLmQvDPBJXWmFXHAfy0f-msCZjXWU35Cvwvlg3A5LZXgmfsJJi-kwYgpjnsOpFqquezAR6mDDnyXC1NLb8HD4PXgb4wbqnz6-jXlj7T0EXaDK0U5JvNtnkVy8y66mucz6tGnANqLYsHIYPT86P2VTOAWPrnz3pJHtoo_2AMI_y5Pc9RiVH1cro5I5D8Qf8_XDBE4oTsNXaqvWItnjg1O-nhxwsRpe-Szbe9wtFLbqjsO0_ZF5jPT1SdNrkuzoRJ4FpJsjBpbSpLB_DXtc0Y15oJVNZwpmJXKywCXRw
2
3
4
5
6
7
8
9
10
11
12
13
# 登录 Kuboard
使用刚才生成的 Token 直接登录即可


# 为什么使用 Kuboard
# 非侵入式
Rancher 为了支持多云管理(MeSos,Docker Swarm,Kubernetes 等),引入了一系列复杂的概念,又需要新增安装 Rancher Server、Rancher Agent 等,本来学习 Kubernetes 已然不堪重负,Rancher 再把多云整合到一起来,无疑又增加了学习和使用的复杂性。如果您只是打算使用 Kubernetes 的话,也许并不需要选择 Rancher 这样重量级的产品。Kuboard 仅仅依赖于原生 Kubernetes,可以运行在各种公有云、私有云上,您也可以自己基于物理机、vsphere、vmware 等已经有的基础设施搭建 Kubernetes,轻松实现现有 Infrastructure 的容器化改造
# 名称空间
在 Kuboard 中,名称空间的展示形式以微服务参考分层架构的形式,将所有的微服务分为如下几层:
- 展现层
- 终端用户访问的 Web 应用
- API 网关层
- Spring Cloud Gateway / Zuul / Kong 等接口网关
- 微服务层
- Spring Boot 微服务,或 PHP / Python / Go 实现的微服务
- 持久层
- MySQL 数据库等(开发及测试环境里,将 MySQL 部署于 Kubernetes 可以极大地降低环境维护的任务量)
- 中间件层
- 消息队列
- 服务注册 Eureka / Zookeeper / Consul 等
- 监控层
- Prometheus + Grafana
- Pinpooint 等

更为贴心的,当某一个工作负载存在部署方面的问题时, Kuboard 将以红色高亮出该工作负载,并配有合适的方式辅助用户定位错误信息
# 无需手写 Yaml
学习 Kubernetes 时,需要花费许多的时间,在理解一个概念之后,模仿着写一个 yaml 文件,再使用 kubectl 应用该文件,使用 Kuboard 提供的工作负载编辑器,可以直观的完成应用的部署

# 应用升级
Kuboard 提供批量修改容器镜像版本的功能,轻松升级应用程序的版本

# k8s踩坑日记
# 不支持 api模式不再支持
将 extensions/v1beta1 换成 app/v1 api即可,相应格式微调。
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-myshop
spec:
selector:
matchLabels:
app: mysql
replicas: 1
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql-myshop
image: mysql:8.0.16
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "aaazzz123"
volumeMounts:
- name: cm-vol-myshop
mountPath: /etc/mysql/conf.d
- name: nfs-vol-myshop
mountPath: /var/lib/mysql
volumes:
- name: cm-vol-myshop
configMap:
name: mysql-myshop-config
items:
- key: mysqld.cnf
path: mysqld.cnf
- name: nfs-vol-myshop
persistentVolumeClaim:
claimName: nfs-pvc-mysql-myshop
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# k8s常用命令
# kubernetes常用命令
# 1、查看类命令
kubectl cluster-info ----查看集群信息
kubectl -s http://localhost:8080 get componentstatuses ----查看各组件信息
kubectl get pods ----列出当前所有的pod
kubectl get pods -o wide ----查看pods所在的运行节点
kubectl get pods -o yaml ----查看pods定义的详细信息
kubectl get rc ----查看Replication Controller信息
kubectl get service ----查看service的信息
kubectl get nodes ----查看节点信息
kubectl get pod --selector name=redis ----按selector名来查找pod
kubectl exec pod名 env ----查看运行的pod的环境变量
# 2、操作类命令
kubectl apply -f 文件名 ----启动
kubectl create -f 文件名 ----创建
kubectl replace -f 文件名 [--force] ----重建
删除:
kubectl delete -f 文件名
kubectl delete pod pod名
kubectl delete rc rc名
kubectl delete service service名
kubectl delete pod --all
kubectl run mybusybox --image=busybox ----启动一个pod
kubectl run mybusybox --image=busybox --replicas=5 ----启动多个pod
kubectl delete deployments mybusybox ----删除创建的pod
kubectl get pods ----列出当前所有的pod
kubectl describe pod [PODNAME] ----查看pod的状态
kubectl run mynginx --image=nginx --port=80 --hostport=8000 ----创建带有端口映射的pod
kubectl run -i --tty busybox --image=busybox ----创建带有终端的pod