k8s 三种 Service

本文简单介绍 k8s 的三种 Service: ClusterIP、NodePort、LoadBalancer。

ClusterIP

ClusterIP 通过集群内部 IP 地址暴露服务,但该地址仅在集群内部可见,无法被集群外部的客户端访问。ClusterIP 是 Service 的默认类型,内部 IP 建议由 k8s 动态指定一个,也支持手动指定。示例:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: nginx-pod-service
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 80
selector:
app: nginx

创建 ClusterIP 后,查看内部 IP,并进行访问:

1
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
root@cloud:~# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d2h <none>
nginx-pod-service ClusterIP 10.100.23.74 <none> 8080/TCP 6s app=nginx
root@cloud:~# curl 10.100.23.74:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

NodePort

NodePort 是 ClusterIP 的增强类型,它在 ClusterIP 的基础之上,在每个节点上使用一个相同的端口号将外部流量引入到该 Service 上来。示例:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: nginx-pod-service
spec:
type: NodePort
ports:
- port: 8080
targetPort: 80
nodePort: 30080
selector:
app: nginx

创建后,在集群外可以通过节点IP:30080访问该服务。

LoadBalancer

LoadBalancer 是 NodePort 的增强类型,为节点上的 NodePort 提供一个外部负载均衡器,需要公有云支持。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: nginx-pod-service
spec:
type: LoadBalancer
ports:
- port: 8080
targetPort: 80
nodePort: 30080
loadBalancerIP: 1.2.3.4
selector:
app: nginx

创建后,在集群外可以通过1.2.3.4:30080访问该服务。

k8s 集群中的 port

本文介绍 k8s 集群外访问集群内部服务不同方式下的 port。

hostPort

出现在 Deployment、Pod 等资源对象描述文件中的容器部分,类似于 docker run -p <containerPort>:<hostPort>。containerPort 为容器暴露的端口;hostPort 为容器暴露的端口直接映射到的主机端口。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: apps/v1
kind: Deployment
...
spec:
...
template:
...
spec:
nodeName: node1
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80 # containerPort是pod内部容器的端口
hostPort: 30080

集群外访问方式:node1的IP:30080

nodePort

出现在 Service 描述文件中,Service 为 NodePort 类型时。port 为在k8s集群内服务访问端口;targetPort 为关联 pod 对外开放端口,与上述 containerPort 保持一致;nodePort 为集群外访问端口,端口范围为 30000-32767。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: nginx-pod-service
labels:
app: nginx
spec:
type: NodePort
ports:
- port: 8080 # port是k8s集群内部访问Service的端口
targetPort: 80 # targetPort是pod的端口,从port和nodePort来的流量经过kube-proxy流入到pod的targetPort上
nodePort: 30080
selector:
app: nginx

集群外访问方式:节点IP:30080

k8s in action—Pod

本章用到的三个 YAML 描述文件如下:

kubia-manual.yaml

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual
spec:
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
protocol: TCP

kubia-manual-with-labels.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual-v2
labels:
creation_mrthod: manual
env: prod
spec:
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
protocol: TCP

kubia-gpu.yaml

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: kubia-gpu
spec:
nodeSelector:
gpu: "true"
containers:
- image: luksa/kubia
name: kubia

apiVersion 描述文件遵循什么版本的 Kubernetes API;
kind 指定资源类型,这里为 pod;
metadata 中的字段描述 pod 名称、标签、注解;
spec 中的字段描述创建容器所需要的镜像、容器名称、监听端口等。

创建 pod

使用 kubectl create -f 命令从 YAML 文件创建 pod

1
kubectl create -f kubia-manual.yaml

得到运行中 pod 的完整定义

1
2
3
4
5
# YAML 格式
kubectl get pod kubia-manual -o yaml

# JSON 格式
kubectl get pod kubia-manual -o json

查看 pod

1
kubectl get pods

查看日志

1
2
3
4
kubectl logs kubia-manual

# 获取多容器 pod 的日志时指定容器名称
kubectl logs kubia-manual -c kubia

将本机的 8888 端口转发至 kubia-manual pod 的 8080 端口

1
kubectl port-forward kubia-manual 8888:8080

使用标签组织 pod

标签时可以附加到资源的任意键值对,用以选择具有该确切标签的资源。

创建带标签的 pod

1
kubectl create -f kubia-manual-with-labels.yaml

基于标签的 pod 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 显示 pod 标签
kubectl get pods --show-labels

# 只显示 env 标签
kubectl get pods -L env

# 为指定的 pod 资源添加新标签
kubectl label pod kubia-manual env=debug

# 修改标签
kubectl label pod kubia-manual env=online --overwrite=true

# - 号删除标签
kubectl label pod kubia-manual env-

使用标签选择器列出 pod

筛选指定值的 pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 筛选 env 为 debug 的 pods
kubectl get pods -l env=debug

# 筛选 creation_method 不为 manual 的 pods
kubectl get pods -l creation_method!=manual

# 筛选不含 env 标签的 pods
kubectl get pods -l '!env'

# in 筛选
kubectl get pods -l 'env in (debug)'

# not in 筛选
kubectl get pods -l 'env notin (debug,online)'

使用标签和选择器约束 pod 调度

使用标签分类工作节点

1
2
kubectl label node gke-kubia gpu=true
kubectl get nodes -l gpu=true

在 kubia-gpu.yaml 中的 spec 添加了 nodeSelector 字段。创建该 pod 时,调度器将只在包含标签 gpu=true 的节点中选择。

注解 pod

注解也是键值对,但与标签不同,不用作标识,用作资源说明,注解可以包含相对更多的数据。

使用 kubectl annotate 添加注解

1
kubectl annotate pod kubia-manual mycompany.com/someannotation="foo bar"

查看注解

1
kubectl describe pod kubia-manual

命名空间

标签会导致资源重叠,可用 namespace 将对象分配到集群级别的隔离区域,相当于多租户的概念,以 namespace 为操作单位。

获取所有 namespace

1
kubectl get ns

获取属于 kube-system 命名空间的 pod

1
kubectl get pods -n kube-system

创建 namespace 资源

1
2
3
4
apiVersion: v1
kind: Namespace
metadata:
name: custom-namespace

创建资源时在 metadata.namespace 中指定资源的命名空间

1
2
3
4
5
6
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual
namespace: custom-namespace
...

使用 kubectl create 命令创建资源时指定命名空间

1
kubectl create -f kubia-manual.yaml -n custom-namespace

切换命名空间

1
kubectl config set-context $(kubectl config current-context) --namespace custom-namespace

删除 pod

删除原理:向 pod 所有容器进程定期发送 SIGTERM 信号,使其正常关闭。如果没有及时关闭,则发送 SIGKILL 强制终止。进程需要正确处理信号,如 Go 注册捕捉信号 signal.Notify() 后 select 监听该 channel。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 按名称删除 pod
kubectl delete po kubia-gpu

# 删除指定标签的 pod
kubectl delete po -l env=debug

# 通过删除整个命名空间来删除 pod
kubectl delete ns custom-namespace

# 删除当前命名空间下的所有 pod(慎用)
kubectl delete pod --all

# 删除当前命名空间的所有资源(慎用)
kubectl delete all --all

CentOS7.6部署k8s

两台 2 核 CPU、2G 内存的阿里云服务器,一台 master 节点,一台 node 节点。

准备工作

关闭防火墙

1
2
systemctl stop firewalld
systemctl disable firewalld

禁用 swap 分区

1
swapoff -a && sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

禁用 SELinux

1
setenforce 0 && sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

时间同步

1
2
systemctl start chronyd
systemctl enable chronyd

重新设置主机名

1
2
3
4
5
6
7
8
9
10
# master 节点
hostnamectl set-hostname master

# node 节点
hostnamectl set-hostname node

vi /etc/hosts
# 添加 ip:
8.130.22.97 master
8.130.23.131 node

将桥接的 IPv4 以及 IPv6 的流量串通

1
2
3
4
5
6
cat >/etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sysctl --system

配置 ipvs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo yum install -y yum-utils
yum install ipset ipvsadmin -y

cat <<EOF> /etc/sysconfig/modules/ipvs.modules
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF

chmod +x /etc/sysconfig/modules/ipvs.modules
/bin/bash /etc/sysconfig/modules/ipvs.modules
lsmod | grep -e ip_vs -e nf_conntrack_ipv4

安装 docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

yum install docker-ce-19.03.2 docker-ce-cli-19.03.2 containerd.io-1.4.4 -y

# 使用 systemd 代替 cgroupfs,配置仓库镜像地址
mkdir /etc/docker
vi /etc/docker/daemon.json
# 添加:
{
"registry-mirrors": ["https://q2hy3fzi.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"]
}

# 启动 docker
systemctl start docker
systemctl enable docker --now

安装 k8s

1
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
cat <<EOF | sudo tee /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
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF

yum install -y kubelet-1.23.1 kubeadm-1.23.1 kubectl-1.23.1 --disableexcludes=kubernetes

vi /etc/sysconfig/kubelet
# 添加:
KUBELET_CGROUP_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"

systemctl enable --now kubelet

# 组件下载脚本
sudo tee ./images.sh <<-'EOF'
#!/bin/bash
images=(
kube-apiserver:v1.23.1
kube-proxy:v1.23.1
kube-controller-manager:v1.23.1
kube-scheduler:v1.23.1
coredns:1.7.5
etcd:3.4.13-0
pause:3.2
kubernetes-dashboard-amd64:v1.10.0
heapster-amd64:v1.5.4
heapster-grafana-amd64:v5.0.4
heapster-influxdb-amd64:v1.5.2
pause-amd64:3.1
)
for imageName in ${images[@]} ; do
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName
done
EOF
# 执行脚本
chmod +x ./images.sh && ./images.sh

部署 k8s master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kubeadm init \
--kubernetes-version v1.23.1 \
--control-plane-endpoint "master:6443" \
--upload-certs \
--image-repository registry.aliyuncs.com/google_containers \
--pod-network-cidr=10.244.0.0/16

# 将执行成功后的命令拷贝过来
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# kubectl
kubectl get node

# 安装插件
curl https://docs.projectcalico.org/manifests/calico.yaml -O
kubectl apply -f calico.yaml

# 查看 node,状态为 ready
kubectl get node

如果安装失败,需要 reset:

1
2
kubeadm reset
rm -rf $HOME/.kube

部署 k8s node

需要执行在 kubeadm init 输出的 kubeadm join 命令:

1
2
3
kubeadm join master:6443 --token hkakru.rnv32cvzw2alodkw \
--discovery-token-ca-cert-hash sha256:49257352b5a320c40785df0b6cc5534e7ae0b6cc758023b52abc0da4b5e9890d \
--control-plane --certificate-key bd218950889df9e03586f4df549a1ec955715d75fb9e576ec75f3287c0004063

部署 dashboard

1
2
3
4
5
6
7
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml

kubectl edit svc kubernetes-dashboard -n kubernetes-dashboard
# type: ClusterIP 改为 type: NodePort

# 查看 dashboard 端口
kubectl get svc -A | grep kubernetes-dashboard

通过 https://集群任意IP:端口 来访问,这里可以是 https://8.130.22.97:31323。

添加用户和绑定角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vi dash.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

生成登录 token

1
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')

将输出的 token 输入到网站中,就可以看到管理界面了。