本章我们将讨论kubernetes中连接到serivce容器的方法,首先我们来看它和docker容器网络工作模式的对比:

  • Docker默认使用主机私有网络通信,因此同一主机的容器之间可以互通。为了实现不同主机之间的容器访问,必须通过绑定本机端口来将请求转发或代理到其他节点容器。这就意味着容器必须协调好它们使用的端口,或者必须动态分配使用主机端口。当项目有多个开发人员时,协调端口及网络安全方面需要消耗更多的时间和人力成本。
  • kubernetes通过创建私有Pod子网,为每个pod提供了唯一的集群私有IP地址,无论Pod在哪个节点,均可互通。

下面我们来用演示如何配置访问kubernetes的Pod。

将Pod暴露给k8s集群

创建一个nginx的Deployment,配置containerPort端口,即可实现从k8s群集中的任何节点访问。

nginx.yml

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 # 指定containerPort端口暴露

创建Deployment

1
kubectl apply -f nginx.yaml

检查Pod正在运行的节点和Pod IP

1
2
3
4
5
kubectl get pods -l run=my-nginx -o wide

NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-jr4a2 1/1 Running 0 13s 10.244.3.4 kubernetes-minion-905m
my-nginx-3800858182-kna2y 1/1 Running 0 13s 10.244.2.5 kubernetes-minion-ljyd

我们可以在k8s任何节点上通过Pod IP和containerPort访问到Pod的运行在80端口上的nginxService,且一个节点可以同时跑多个containerPort端口相同的Service而不会冲突。同样,我们仍然可以将Pod的端口映射到主机节点上,但由于k8s的上述特性,现在很少这么做。

创建Service

由上可知,我们通过部署Deployment就可以直接与这些pod进行通信,但是当节点挂掉时,pod会随之消亡,Deployment将会创建具有不同IP的新节点;nginx的访问IP也随之变更了,怎么样才能让访问方式保持不变?这就要用到Service。

Kubernetes Service是一种抽象资源,它定义了在k8s集群中运行的相同逻辑Pod集合,它们都提供相同的功能。当Service创建时,k8s会为每个Service分配一个唯一的IP地址(也称为clusterIP)。 此地址与Service的生命周期相关,并且在Service处于活动状态时不会变更。可配置对Service的通信请求自动负载均衡到Service成员的pod上。

可以使用kubectl公开为2个nginx副本创建Service:

1
2
kubectl expose deployment my-nginx
service/my-nginx exposed

但通常使用yaml配置文件通过kubectl apply创建

nginx-svc.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service # 创建Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80 # 指定Service暴露端口
protocol: TCP # 端口类型
selector: # Pod选择器
run: my-nginx
1
kubectl apply -f nginx-svc.yaml

此Service使用run:my-nginx标签匹配任何Pod上的TCP端口80,并在抽象的Service端口80上暴露给整个k8s集群(targetPort:是容器接受流量的端口,port:是抽象的Service端口 ,可以是其他pod用于访问Service的任何端口)。

查看创建的Service

1
2
3
4
kubectl get svc my-nginx

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.0.162.149 <none> 80/TCP 21s

如前所述,Service由一组Pod支持。 这些Pod通过Endpoint端点(此处为Pod IP和containerPort)公开。集群将不停的检查Service的选择器,并将结果发布到名为my-nginx的端点对象。当Pod死亡时,它会自动从端点移除,与Service选择器匹配的新Pod将自动添加到端点。

查看Service详情

1
2
3
4
5
6
7
8
9
10
11
12
13
kubectl describe svc my-nginx

Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP: 10.0.162.149
Port: <unset> 80/TCP
Endpoints: 10.244.2.5:80,10.244.3.4:80
Session Affinity: None
Events: <none>
1
2
3
4
kubectl get ep my-nginx

NAME ENDPOINTS AGE
my-nginx 10.244.2.5:80,10.244.3.4:80 1m

现在可以从k8s群集中的任何节点通过的:访问nginx服务。 请注意,Serivce IP是完全虚拟的。

访问Service

Kubernetes支持2种查找Service的主要模式 - 环境变量和DNS。 前者内置在k8s中,而后者需要CoreDNS群集插件支持。详见Kubernetes之Service。现在普遍使用DNS方式实现。

Service安全

到目前为止,我们只是从群集中访问到nginx服务。 在将服务暴露给互联网之前,需要确保Service通信渠道是安全的。因此,一般建议配置:

  • https的自签名证书(除非您已拥有身份证书)
  • 带证书的nginx服务
  • pod间访问证书的secret密钥

例如:假设证书创建

1
2
3
4
5
#create a public private key pair
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
#convert the keys to base64 encoding
cat /d/tmp/nginx.crt | base64
cat /d/tmp/nginx.key | base64

使用上述输出配置文件nginxsecrets.yaml

1
2
3
4
5
6
7
8
apiVersion: "v1"
kind: "Secret"
metadata:
name: "nginxsecret"
namespace: "default"
data:
nginx.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQUp5M3lQK0pzMlpJTUEwR0NTcUdTSWIzRFFFQkJRVUFNQ1l4RVRBUEJnTlYKQkFNVENHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0V3aHVaMmx1ZUhOMll6QWVGdzB4TnpFd01qWXdOekEzTVRKYQpGdzB4T0RFd01qWXdOekEzTVRKYU1DWXhFVEFQQmdOVkJBTVRDRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtFd2h1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjFxSU1SOVdWM0IKMlZIQlRMRmtobDRONXljMEJxYUhIQktMSnJMcy8vdzZhU3hRS29GbHlJSU94NGUrMlN5ajBFcndCLzlYTnBwbQppeW1CL3JkRldkOXg5UWhBQUxCZkVaTmNiV3NsTVFVcnhBZW50VWt1dk1vLzgvMHRpbGhjc3paenJEYVJ4NEo5Ci82UVRtVVI3a0ZTWUpOWTVQZkR3cGc3dlVvaDZmZ1Voam92VG42eHNVR0M2QURVODBpNXFlZWhNeVI1N2lmU2YKNHZpaXdIY3hnL3lZR1JBRS9mRTRqakxCdmdONjc2SU90S01rZXV3R0ljNDFhd05tNnNTSzRqYUNGeGpYSnZaZQp2by9kTlEybHhHWCtKT2l3SEhXbXNhdGp4WTRaNVk3R1ZoK0QrWnYvcW1mMFgvbVY0Rmo1NzV3ajFMWVBocWtsCmdhSXZYRyt4U1FVQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjcKTUI4R0ExVWRJd1FZTUJhQUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjdNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBRVhTMW9FU0lFaXdyMDhWcVA0K2NwTHI3TW5FMTducDBvMm14alFvCjRGb0RvRjdRZnZqeE04Tzd2TjB0clcxb2pGSW0vWDE4ZnZaL3k4ZzVaWG40Vm8zc3hKVmRBcStNZC9jTStzUGEKNmJjTkNUekZqeFpUV0UrKzE5NS9zb2dmOUZ3VDVDK3U2Q3B5N0M3MTZvUXRUakViV05VdEt4cXI0Nk1OZWNCMApwRFhWZmdWQTRadkR4NFo3S2RiZDY5eXM3OVFHYmg5ZW1PZ05NZFlsSUswSGt0ejF5WU4vbVpmK3FqTkJqbWZjCkNnMnlwbGQ0Wi8rUUNQZjl3SkoybFIrY2FnT0R4elBWcGxNSEcybzgvTHFDdnh6elZPUDUxeXdLZEtxaUMwSVEKQ0I5T2wwWW5scE9UNEh1b2hSUzBPOStlMm9KdFZsNUIyczRpbDlhZ3RTVXFxUlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
nginx.key: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2RhaURFZlZsZHdkbFIKd1V5eFpJWmVEZWNuTkFhbWh4d1NpeWF5N1AvOE9ta3NVQ3FCWmNpQ0RzZUh2dGtzbzlCSzhBZi9WemFhWm9zcApnZjYzUlZuZmNmVUlRQUN3WHhHVFhHMXJKVEVGSzhRSHA3VkpMcnpLUC9QOUxZcFlYTE0yYzZ3MmtjZUNmZitrCkU1bEVlNUJVbUNUV09UM3c4S1lPNzFLSWVuNEZJWTZMMDUrc2JGQmd1Z0ExUE5JdWFubm9UTWtlZTRuMG4rTDQKb3NCM01ZUDhtQmtRQlAzeE9JNHl3YjREZXUraURyU2pKSHJzQmlIT05Xc0RadXJFaXVJMmdoY1kxeWIyWHI2UAozVFVOcGNSbC9pVG9zQngxcHJHclk4V09HZVdPeGxZZmcvbWIvNnBuOUYvNWxlQlkrZStjSTlTMkQ0YXBKWUdpCkwxeHZzVWtGQWdNQkFBRUNnZ0VBZFhCK0xkbk8ySElOTGo5bWRsb25IUGlHWWVzZ294RGQwci9hQ1Zkank4dlEKTjIwL3FQWkUxek1yall6Ry9kVGhTMmMwc0QxaTBXSjdwR1lGb0xtdXlWTjltY0FXUTM5SjM0VHZaU2FFSWZWNgo5TE1jUHhNTmFsNjRLMFRVbUFQZytGam9QSFlhUUxLOERLOUtnNXNrSE5pOWNzMlY5ckd6VWlVZWtBL0RBUlBTClI3L2ZjUFBacDRuRWVBZmI3WTk1R1llb1p5V21SU3VKdlNyblBESGtUdW1vVlVWdkxMRHRzaG9reUxiTWVtN3oKMmJzVmpwSW1GTHJqbGtmQXlpNHg0WjJrV3YyMFRrdWtsZU1jaVlMbjk4QWxiRi9DSmRLM3QraTRoMTVlR2ZQegpoTnh3bk9QdlVTaDR2Q0o3c2Q5TmtEUGJvS2JneVVHOXBYamZhRGR2UVFLQmdRRFFLM01nUkhkQ1pKNVFqZWFKClFGdXF4cHdnNzhZTjQyL1NwenlUYmtGcVFoQWtyczJxWGx1MDZBRzhrZzIzQkswaHkzaE9zSGgxcXRVK3NHZVAKOWRERHBsUWV0ODZsY2FlR3hoc0V0L1R6cEdtNGFKSm5oNzVVaTVGZk9QTDhPTm1FZ3MxMVRhUldhNzZxelRyMgphRlpjQ2pWV1g0YnRSTHVwSkgrMjZnY0FhUUtCZ1FEQmxVSUUzTnNVOFBBZEYvL25sQVB5VWs1T3lDdWc3dmVyClUycXlrdXFzYnBkSi9hODViT1JhM05IVmpVM25uRGpHVHBWaE9JeXg5TEFrc2RwZEFjVmxvcG9HODhXYk9lMTAKMUdqbnkySmdDK3JVWUZiRGtpUGx1K09IYnRnOXFYcGJMSHBzUVpsMGhucDBYSFNYVm9CMUliQndnMGEyOFVadApCbFBtWmc2d1BRS0JnRHVIUVV2SDZHYTNDVUsxNFdmOFhIcFFnMU16M2VvWTBPQm5iSDRvZUZKZmcraEppSXlnCm9RN3hqWldVR3BIc3AyblRtcHErQWlSNzdyRVhsdlhtOElVU2FsbkNiRGlKY01Pc29RdFBZNS9NczJMRm5LQTQKaENmL0pWb2FtZm1nZEN0ZGtFMXNINE9MR2lJVHdEbTRpb0dWZGIwMllnbzFyb2htNUpLMUI3MkpBb0dBUW01UQpHNDhXOTVhL0w1eSt5dCsyZ3YvUHM2VnBvMjZlTzRNQ3lJazJVem9ZWE9IYnNkODJkaC8xT2sybGdHZlI2K3VuCnc1YytZUXRSTHlhQmd3MUtpbGhFZDBKTWU3cGpUSVpnQWJ0LzVPbnlDak9OVXN2aDJjS2lrQ1Z2dTZsZlBjNkQKckliT2ZIaHhxV0RZK2Q1TGN1YSt2NzJ0RkxhenJsSlBsRzlOZHhrQ2dZRUF5elIzT3UyMDNRVVV6bUlCRkwzZAp4Wm5XZ0JLSEo3TnNxcGFWb2RjL0d5aGVycjFDZzE2MmJaSjJDV2RsZkI0VEdtUjZZdmxTZEFOOFRwUWhFbUtKCnFBLzVzdHdxNWd0WGVLOVJmMWxXK29xNThRNTBxMmk1NVdUTThoSDZhTjlaMTltZ0FGdE5VdGNqQUx2dFYxdEYKWSs4WFJkSHJaRnBIWll2NWkwVW1VbGc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"

创建secret证书和密钥

1
2
kubectl apply -f nginxsecrets.yaml
secret/nginxsecret created

查看secret

1
2
3
4
5
kubectl get secrets

NAME TYPE DATA AGE
default-token-il9rc kubernetes.io/service-account-token 1 1d
nginxsecret Opaque 2 1m

修改nginx服务,使用secret中的证书启动https服务,暴露两个端口(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 # 配置NodePort节点端口映射
ports:
- port: 8080
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 # 此处挂载secret
name: secret-volume # 卷名

上述重要配置信息:

  • 创建Deployment和Service
  • nginx Service同时监听80和443
  • nginx每个容器都通过/etc/nginx/ssl挂载创建的密钥和证书secret卷

然后你可以通过Pod或ClusterIP访问到nginx服务

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>

暴露Service服务

经常你需要将应用程序公开到外部IP地址,方便业务流量接入访问。 为此,Kubernetes支持两种方式:NodePortsLoadBalancers。 在上一节中创建的Service服务已使用NodePort,因此如果你的节点具有公网IP,则你的nginx HTTPS副本已经可以通过Internet访问。

NodePorts

获取ClusterIP和nodePort端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kubectl get svc my-nginx -o yaml | grep nodePort -C 5
uid: 07191fb3-f61a-11e5-8ae5-42010af00002
spec:
clusterIP: 10.0.162.149
ports:
- name: http
nodePort: 31704
port: 8080
protocol: TCP
targetPort: 80
- name: https
nodePort: 32453
port: 443
protocol: TCP
targetPort: 443
selector:
run: my-nginx

测试你的nginx服务

1
2
3
4
5
6
7
8
9
10
11
12
13
kubectl get nodes -o yaml | grep ExternalIP -C 1
- address: 104.197.41.11
type: ExternalIP
allocatable:
--
- address: 23.251.152.56
type: ExternalIP
allocatable:
...

$ curl https://<EXTERNAL-IP>:<NODE-PORT> -k
...
<h1>Welcome to nginx!</h1>

LoadBalancer

现在我们使用LoadBalancer方式重新创建服务,只需将my-nginx服务的类型从NodePort更改为LoadBalancer即可

1
2
3
4
5
kubectl edit svc my-nginx
kubectl get svc my-nginx

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.0.162.149 162.222.184.144 80/TCP,81/TCP,82/TCP 21s

测试

1
2
3
curl https://<EXTERNAL-IP> -k
...
<title>Welcome to nginx!</title>

EXTERNAL-IP列中的IP地址是公共互联网上可用的IP地址。 CLUSTER-IP仅在您的群集/私有云网络中可用。

请注意,在AWS上,键入LoadBalancer会创建一个ELB,它使用(长)主机名,而不是IP。实际上,要适应标准的kubectl获取svc输出太长了,所以你需要做kubectl描述服务my-nginx才能看到它。你会看到这样的事情:

1
2
3
4
kubectl describe service my-nginx
...
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
...