本章我们将讨论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
|
创建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 metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP selector: 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 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 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支持两种方式:NodePorts
和LoadBalancers
。 在上一节中创建的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 ...
|