0%

K8s-Service连接应用程序

创建/访问/Service,保护 Service 安全,暴露 Service;

Service连接应用程序

Kubernetes 的网络模型

通常,Docker 使用一种 host-private 的联网方式,在此情况下,只有两个容器都在同一个节点(主机)上时,一个容器才可以通过网络连接另一个容器。为了使 Docker 容器可以跨节点通信,必须在宿主节点(主机)的 IP 地址上分配端口,并将该端口接收到的网络请求转发(或代理)到容器中。这意味着,用户必须非常小心地为容器分配宿主节点(主机)的端口号,或者端口号可以自动分配。

在一个集群中,多个开发者之间协调分配端口号是非常困难的。Kubernetes 认为集群中的两个 Pod 应该能够互相通信,无论他们各自在哪个节点上。每一个 Pod 都被分配自己的 “cluster-private-IP”,因此,无需在 Pod 间建立连接,或者将容器的端口映射到宿主机的端口。因此:

  • Pod 中的任意容器可以使用 localhost 直连同 Pod 中另一个容器的端口
  • 集群中的任意 Pod 可以使用另一的 Pod 的 cluster-private-IP 直连对方的端口,(无需 NAT 映射)

在集群中部署 Pod

创建文件 run-my-nginx.yaml,文件内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80

执行以下命令,部署 Pod 并检查运行情况:

1
2
3
4
5
6
7
[root@k8s-master 0408]# kubectl apply -f  run-my-nginx.yaml 
deployment.apps/my-nginx created

[root@k8s-master 0408]# kubectl get pods -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-75897978cd-6vfrl 0/1 ErrImagePull 0 39s 10.244.235.206 k8s-master <none> <none>
my-nginx-75897978cd-p2tcd 1/1 Running 0 39s 10.244.44.235 k8s-node-02 <none> <none>

执行命令 kubectl get pods -l run=my-nginx -o yaml | grep podIP, 检查 Pod 的 IP 地址,输出结果如下:

1
2
3
4
5
6
7
[root@k8s-master 0408]# kubectl get pods -l run=my-nginx -o yaml | grep podIP
cni.projectcalico.org/podIP: 10.244.235.206/32
podIP: 10.244.235.206
podIPs:
cni.projectcalico.org/podIP: 10.244.44.235/32
podIP: 10.244.44.235
podIPs:

在集群中的任意节点上,您可以执行 curl 10.244.235.206 或curl 10.244.44.235` 获得 nginx 的响应。此时:

  • 容器并没有使用节点上的 80 端口
  • 没有使用 NAT 规则对容器端口进行映射

这意味着,您可以

  • 在同一节点上使用 80 端口运行多个 nginx Pod
  • 在集群的任意节点/Pod 上使用 nginx Pod 的 clusterIP 访问 nginx 的 80 端口

同 Docker 一样,Kubernets 中,仍然可以将 Pod 的端口映射到宿主节点的网络地址上(使用 nodePort),但是使用 Kubernetes 的网络模型时,这类需求已经大大减少了。

创建 Service

Pod 因为故障或其他原因终止后,Deployment Controller 将创建一个新的 Pod 以替代该 Pod,但是 IP 地址将发生变化。Kubernetes Service 解决了这样的问题。

Kubernetes Service:

  • 定义了集群中一组 Pod 的逻辑集合,该集合中的 Pod 提供了相同的功能
  • 被创建后,获得一个唯一的 IP 地址(ClusterIP)。直到该 Service 被删除,此地址不会发生改变
  • Pod 可以直接连接 Service IP 地址上的端口,且发送到该 IP 地址的网络请求被自动负载均衡分发到 Service 所选取的 Pod 集合中

执行命令 kubectl expose deployment/my-nginx 可以为上面的两个 nginx Pod 创建 Service,输出结果如下所示:

1
2
[root@k8s-master 0408]# kubectl expose deployment/my-nginx
service/my-nginx exposed

该命令等价于 kubectl apply -f nginx-svc.yaml,其中 nginx-svc.yaml 文件的内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
run: my-nginx

该 yaml 文件将创建一个 Service:

  • 该 Service 通过 label selector 选取包含 run: my-nginx 标签的 Pod 作为后端 Pod
  • 该 Service 暴露一个端口 80(spec.ports[*].port
  • 该 Service 将 80 端口上接收到的网络请求转发到后端 Pod 的 80 (spec.ports[*].targetPort)端口上,支持负载均衡
1
2
3
[root@k8s-master 0408]# kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.97.72.97 <none> 80/TCP 28m

Service 的后端 Pod 实际上通过 Endpoints 来暴露。Kubernetes 会持续检查 Service 的 label selector spec.selector,并将符合条件的 Pod 更新到与 Service 同名(my-nginx)的 Endpoints 对象。如果 Pod 终止了,该 Pod 将被自动从 Endpoints 中移除,新建的 Pod 将自动被添加到该 Endpoint。

执行命令 kubectl describe svc my-nginx,输出结果如下,请注意 Endpoints 中的 IP 地址与上面获得的 Pod 地址相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-master 0408]# kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: <none>
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP: 10.97.72.97
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.154.209:80,10.244.44.236:80
Session Affinity: None
Events: <none

执行命令 kubectl get ep my-nginx,输出结果如下:

1
2
3
[root@k8s-master 0408]# kubectl get ep my-nginx
NAME ENDPOINTS AGE
my-nginx 10.244.154.209:80,10.244.44.236:80 38m

此时,可以在集群的任意节点上执行 curl 10.0.162.149:80,通过 Service 的 ClusterIP:Port 访问 nginx。

Service 的 IP 地址是虚拟地址。

访问 Service

Kubernetes 支持两种方式发现服务:

  • 环境变量
  • DNS

环境变量

针对每一个有效的 Service,kubelet 在创建 Pod 时,向 Pod 添加一组环境变量。这种做法引发了一个 Pod 和 Service 的顺序问题。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master 0408]# kubectl exec my-nginx-75897978cd-4t7zb  -- printenv | grep SERVICE
MY_SERVICE_PORT_80_TCP_PORT=80
MY_SERVICE_PORT=tcp://10.100.240.247:80
MY_SERVICE_SERVICE_HOST=10.100.240.247
MY_SERVICE_PORT_80_TCP=tcp://10.100.240.247:80
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
NGINX_DEP_SERVICE_PORT_80_80=80
MY_SERVICE_PORT_80_TCP_ADDR=10.100.240.247
MYAPP_SERVICE_PORT_80_80=80
MY_SERVICE_SERVICE_PORT=80
MYAPP_SERVICE_HOST=10.102.48.254
MYAPP_SERVICE_PORT=80
NGINX_DEP_SERVICE_HOST=10.98.115.52
NGINX_DEP_SERVICE_PORT=80
MY_SERVICE_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_HOST=10.96.0.1

DNS

Kubernetes 提供了一个 DNS cluster addon,可自动为 Service 分配 DNS name。该 addon 已经默认安装。

执行命令 kubectl get services kube-dns --namespace=kube-system 查看该 addon 在集群上是否可用,输出结果如下所示:

1
2
3
[root@k8s-master 0408]# kubectl get services kube-dns --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 13d

此时,可以从集群中任何 Pod 中按 Service 的名称访问该 Service。

  • 执行命令 kubectl run curl --image=radial/busyboxplus:curl -i --tty 获得 busyboxplus 容器的命令行终端,该命令输出结果如下所示:
1
2
3
[root@k8s-master 0408]# kubectl run curl --image=radial/busyboxplus:curl -i --tty
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.
If you don't see a command prompt, try pressing enter.
  • 执行命令 nslookup my-nginx,输出结果如下所示:
1
2
3
4
5
6
[ root@curl-69c656fd45-848f6:/ ]$ nslookup my-nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name: my-nginx
Address 1: 10.97.72.97 my-nginx.default.svc.cluster.local
  • 执行命令 curl my-nginx:80,可获得 Nginx 的响应。

  • 执行命令 kubectl delete deployment curl 可删除刚才创建的 curl 测试容器

保护 Service 的安全

在将该 Service 公布到互联网时,可能需要确保该通信渠道是安全的。为此:

  • 准备 https 证书(购买,或者自签名)
  • 将该 nginx 服务配置好,并使用该 https 证书
  • 配置 Secret,以使得其他 Pod 可以使用该证书

配置 nginx 使用自签名证书:

  • 创建密钥对
1
2
3
4
5
[root@k8s-master 0408]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /tmp/nginx.key -out /tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
Generating a 2048 bit RSA private key
.............................................+++
.............................................+++
writing new private key to '/tmp/nginx.key'
  • 将密钥对转换为 base64 编码
1
2
cat /tmp/nginx.crt | base64
cat /tmp/nginx.key | base64
  • 创建一个如下格式的 nginxsecrets.yaml 文件,使用前面命令输出的 base64 编码替换其中的内容(base64编码内容不能换行)
1
2
3
4
5
6
7
8
apiVersion: "v1"
kind: "Secret"
metadata:
name: "nginxsecret"
namespace: "default"
data:
nginx.crt: ""
nginx.key: ""
  • 使用该文件创建 Secrets
1
2
3
4
5
6
7
8
9
#创建 Secrets
[root@k8s-master 0408]# kubectl apply -f 1.yaml
secret/nginxsecret created

#查看 Secrets
[root@k8s-master 0408]# kubectl get secrets
NAME TYPE DATA AGE
default-token-xws5p kubernetes.io/service-account-token 3 13d
nginxsecret Opaque 2 38s
  • 修改 nginx 部署,使 nginx 使用 Secrets 中的 https 证书,修改 Service,使其暴露 80 端口和 443端口。nginx-secure-app.yaml 文件如下所示:
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
46
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
- port: 443
protocol: TCP
name: https
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
volumes:
- name: secret-volume
secret:
secretName: nginxsecret #####
containers:
- name: nginxhttps
image: bprashanth/nginxhttps:1.0
ports:
- containerPort: 443
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/ssl #####
name: secret-volume

关于 nginx-secure-app.yaml

  • 该文件同时包含了 Deployment 和 Service 的定义
  • nginx server 监听 HTTP 80 端口和 HTTPS 443 端口的请求, nginx Service 同时暴露了这两个端口
  • nginx 容器可以通过 /etc/nginx/ssl 访问到 https 证书,https 证书存放在 Secrets 中,且必须在 Pod 创建之前配置好。

执行命令使该文件生效:

1
2
kubectl delete deployments,svc my-nginx
kubectl create -f ./nginx-secure-app.yaml

此时,可以从任何节点访问该 nginx server

1
2
3
4
5
kubectl get pods -o yaml | grep -i podip
podIP: 10.244.3.5
node $ curl -k https://10.244.3.5
...
<h1>Welcome to nginx!</h1>

创建 curlpod.yaml 文件,内容如下:

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: curl-deployment
spec:
selector:
matchLabels:
app: curlpod
replicas: 1
template:
metadata:
labels:
app: curlpod
spec:
volumes:
- name: secret-volume
secret:
secretName: nginxsecret
containers:
- name: curlpod
command:
- sh
- -c
- while true; do sleep 1; done
image: radial/busyboxplus:curl
volumeMounts:
- mountPath: /etc/nginx/ssl
name: secret-volume
  • 执行命令,完成 curlpod 的部署
1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master 0408]# kubectl apply -f curlpod.yaml 
deployment.apps/curl-deployment created

[root@k8s-master 0408]# kubectl get pods -l app=curlpod
NAME READY STATUS RESTARTS AGE
curl-deployment-f8c5c685b-cwqrp 1/1 Running 0 17s

#执行 curl,访问 nginx 的 https 端口
[root@k8s-master 0408]# kubectl exec curl-deployment-f8c5c685b-cwqrp -- curl https://my-nginx --cacert /etc/nginx/ssl/nginx.crt
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:--

暴露 Service

在应用程序中,可能有一部分功能需要通过 Service 发布到一个外部的 IP 地址上。Kubernetes 支持如下两种方式:

  • NodePort
  • LoadBalancer
    • 需要云环境支持

执行命令查看service暴露的外部端口

  • [root@k8s-master 0408]# kubectl get svc my-nginx -o yaml | grep nodePort -C 5
    spec:
      clusterIP: 10.107.66.92
      externalTrafficPolicy: Cluster
      ports:
    
      - name: http
        nodePort: 30407      ###    
        port: 80
        protocol: TCP
        targetPort: 80
      - name: https
        nodePort: 30534      ###
        port: 443
        protocol: TCP
        targetPort: 443
          selector:
        run: my-nginx
    
    1
    2
    3
    4
    5
    6

    假设某一节点的公网 IP 地址为 23.251.152.56,可以在任意一台可上网的机器执行命令 `curl https://23.251.152.56:30407 -k`。输出结果为:

    ```text
    ...
    <h1>Welcome to nginx!</h1>