39-Connecting Applications with Services

Connecting Applications with Services

The Kubernetes model for connecting containers

Now that you have a continuously running, replicated application you can expose it on a network. Before discussing the Kubernetes approach to networking, it is worthwhile to contrast it with the “normal” way networking works with Docker. 现在您有了一个持续运行的复制应用程序,您可以在网络上公开它。在讨论Kubernetes的联网方法之前,值得将其与Docker的“正常”联网方式进行对比。

By default, Docker uses host-private networking, so containers can talk to other containers only if they are on the same machine. In order for Docker containers to communicate across nodes, there must be allocated ports on the machine’s own IP address, which are then forwarded or proxied to the containers. This obviously means that containers must either coordinate which ports they use very carefully or ports must be allocated dynamically. 默认情况下,Docker使用主机专用网络,因此容器只有在同一台计算机上才能与其他容器通信。为了使DOCKER容器跨节点通信,必须在机器自己的IP地址上分配端口,然后将这些端口转发或代理到容器。这显然意味着容器必须非常小心地协调它们使用的端口,或者必须动态地分配端口。

Coordinating ports across multiple developers is very difficult to do at scale and exposes users to cluster-level issues outside of their control. Kubernetes assumes that pods can communicate with other pods, regardless of which host they land on. We give every pod its own cluster-private-IP address so you do not need to explicitly create links between pods or map container ports to host ports. This means that containers within a Pod can all reach each other’s ports on localhost, and all pods in a cluster can see each other without NAT. The rest of this document will elaborate on how you can run reliable services on such a networking model. 跨多个开发人员协调端口在规模上是非常困难的,并且会让用户暴露在他们无法控制的集群级问题中。kubernetes假设豆荚可以与其他豆荚通信,而不管它们降落在哪个宿主上。我们为每个pod提供自己的集群专用ip地址,因此您不需要显式地在pod之间创建链接,也不需要将容器端口映射到主机端口。这意味着pod中的容器都可以到达本地主机上彼此的端口,集群中的所有pod都可以在没有nat的情况下看到彼此。本文的其余部分将详细说明如何在这样的网络模型上运行可靠的服务。

This guide uses a simple nginx server to demonstrate proof of concept. The same principles are embodied in a more complete [Jenkins CI application] 本指南使用一个简单的nginx服务器来演示概念证明。同样的原则体现在Jenkins CI application.

Exposing pods to the cluster

We did this in a previous example, but let’s do it once again and focus on the networking perspective. Create an nginx Pod, and note that it has a container port specification 我们在前面的例子中做了这一点,但是让我们再次做一次,重点放在网络视角上。创建一个nginx pod,并注意它有一个容器端口规范:

service/networking/run-my-nginx.yaml Copy service/networking/run-my-nginx.yaml to clipboard

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

This makes it accessible from any node in your cluster. Check the nodes the Pod is running on 这使得它可以从集群中的任何节点访问。检查pod运行的节点:

kubectl apply -f ./run-my-nginx.yaml
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

Check your pods’ IPs:

kubectl get pods -l run=my-nginx -o yaml | grep podIP
    podIP: 10.244.3.4
    podIP: 10.244.2.5

You should be able to ssh into any node in your cluster and curl both IPs. Note that the containers are not using port 80 on the node, nor are there any special NAT rules to route traffic to the pod. This means you can run multiple nginx pods on the same node all using the same containerPort and access them from any other pod or node in your cluster using IP. Like Docker, ports can still be published to the host node’s interfaces, but the need for this is radically diminished because of the networking model. 您应该能够通过ssh连接到集群中的任何节点并卷曲两个ip。请注意,容器没有在节点上使用端口80,也没有任何特殊的nat规则将通信路由到pod。这意味着您可以使用相同的containerport在同一个节点上运行多个nginx pod,并使用ip从集群中的任何其他pod或节点访问它们。像Docker一样,端口仍然可以发布到主机节点的接口上,但是由于网络模型的原因,对端口的需求大大减少。

You can read more about how we achieve this if you’re curious. 如果你好奇的话,你可以阅读更多关于我们如何实现这一目标的文章。

Creating a Service

So we have pods running nginx in a flat, cluster wide, address space. In theory, you could talk to these pods directly, but what happens when a node dies? The pods die with it, and the Deployment will create new ones, with different IPs. This is the problem a Service solves. 所以我们有在一个平坦的、集群范围内的地址空间中运行nginx的pods。理论上,你可以直接和这些豆荚交谈,但是当一个节点死亡时会发生什么呢?豆荚随之消亡,部署将创建新的,具有不同IP的豆荚。这是服务解决的问题。

A Kubernetes Service is an abstraction which defines a logical set of Pods running somewhere in your cluster, that all provide the same functionality. When created, each Service is assigned a unique IP address (also called clusterIP). This address is tied to the lifespan of the Service, and will not change while the Service is alive. Pods can be configured to talk to the Service, and know that communication to the Service will be automatically load-balanced out to some pod that is a member of the Service. kubernetes服务是一个抽象,它定义了在集群中某个地方运行的一组逻辑pod,这些pod都提供相同的功能。创建时,为每个服务分配一个唯一的IP地址(也称为clusterip)。此地址与服务的生命周期相关,并且在服务处于活动状态时不会更改。pods可以配置为与服务对话,并且知道与服务的通信将自动负载平衡到属于服务的某个pod。

You can create a Service for your 2 nginx replicas with kubectl expose: 您可以使用kubectl-expose为2个nginx副本创建服务:

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

This is equivalent to kubectl apply -f the following yaml 这相当于kubectl apply-f the following yaml:

service/networking/nginx-svc.yaml Copy service/networking/nginx-svc.yaml to clipboard

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx

This specification will create a Service which targets TCP port 80 on any Pod with the run: my-nginx label, and expose it on an abstracted Service port (targetPort: is the port the container accepts traffic on, port: is the abstracted Service port, which can be any port other pods use to access the Service). View Service API object to see the list of supported fields in service definition. Check your Service: 此规范将创建一个服务,该服务以任何带有run:my nginx标签的pod上的tcp端口80为目标,并将其公开在抽象的服务端口(targetport:是容器接受流量的端口,port:是抽象的服务端口,它可以是其他pod用于访问服务的任何端口)。查看服务API对象以查看服务定义中受支持字段的列表。检查您的服务:

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

As mentioned previously, a Service is backed by a group of Pods. These Pods are exposed through endpoints. The Service’s selector will be evaluated continuously and the results will be POSTed to an Endpoints object also named my-nginx. When a Pod dies, it is automatically removed from the endpoints, and new Pods matching the Service’s selector will automatically get added to the endpoints. Check the endpoints, and note that the IPs are the same as the Pods created in the first step: 如前所述,服务由一组pod支持。这些pod通过端点暴露。服务的选择器将被持续评估,结果将被发布到一个endpoints对象(也称为my nginx)。当pod死亡时,它将自动从端点中移除,并且与服务选择器匹配的新pod将自动添加到端点。检查端点,并注意IP与第一步中创建的播客相同:

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>
kubectl get ep my-nginx
NAME       ENDPOINTS                     AGE
my-nginx   10.244.2.5:80,10.244.3.4:80   1m

You should now be able to curl the nginx Service on : from any node in your cluster. Note that the Service IP is completely virtual, it never hits the wire. If you’re curious about how this works you can read more about the service proxy. 现在,您应该能够从集群中的任何节点将nginx服务卷曲到上。请注意,服务IP是完全虚拟的,它从不连接。如果您想知道这是如何工作的,可以阅读有关服务代理的更多信息。

Accessing the Service

Kubernetes supports 2 primary modes of finding a Service - environment variables and DNS. The former works out of the box while the latter requires the CoreDNS cluster addon. kubernetes支持查找服务环境变量和dns的两种主要模式。前者是开箱即用的,而后者则需要coredns集群插件。

Note: If the service environment variables are not desired (because possible clashing with expected program ones, too many variables to process, only using DNS, etc) you can disable this mode by setting the enableServiceLinks flag to false on the pod spec. 如果不需要服务环境变量(因为可能与预期的程序变量冲突、要处理的变量太多、仅使用dns等),可以通过在pod规范中将enableservicelinks标志设置为false来禁用此模式。

Environment Variables

When a Pod runs on a Node, the kubelet adds a set of environment variables for each active Service. This introduces an ordering problem. To see why, inspect the environment of your running nginx Pods (your Pod name will be different): 当pod在节点上运行时,kubelet为每个活动服务添加一组环境变量。这就引入了排序问题。要了解原因,请检查运行nginx pods的环境(您的pod名称将不同):

kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443

Note there’s no mention of your Service. This is because you created the replicas before the Service. Another disadvantage of doing this is that the scheduler might put both Pods on the same machine, which will take your entire Service down if it dies. We can do this the right way by killing the 2 Pods and waiting for the Deployment to recreate them. This time around the Service exists before the replicas. This will give you scheduler-level Service spreading of your Pods (provided all your nodes have equal capacity), as well as the right environment variables: 注意没有提到你的服务。这是因为您在服务之前创建了副本。这样做的另一个缺点是调度器可能会将两个pod放在同一台机器上,如果它死了,这将导致整个服务瘫痪。我们可以通过杀死2个吊舱并等待部署重新创建它们来正确地完成这项工作。这一次的服务存在于复制品之前。这将为您提供播客的调度级服务扩展(前提是您的所有节点都具有相同的容量),以及正确的环境变量:

kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;

kubectl get pods -l run=my-nginx -o wide
NAME                        READY     STATUS    RESTARTS   AGE     IP            NODE
my-nginx-3800858182-e9ihh   1/1       Running   0          5s      10.244.2.7    kubernetes-minion-ljyd
my-nginx-3800858182-j4rm4   1/1       Running   0          5s      10.244.3.8    kubernetes-minion-905m

You may notice that the pods have different names, since they are killed and recreated. 你可能会注意到,豆荚有不同的名字,因为他们被杀死和重新创造。

kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_HOST=10.0.162.149
KUBERNETES_SERVICE_HOST=10.0.0.1
MY_NGINX_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT_HTTPS=443

DNS

Kubernetes offers a DNS cluster addon Service that automatically assigns dns names to other Services. You can check if it’s running on your cluster: kubernetes提供了一个dns集群插件服务,它自动将dns名称分配给其他服务。您可以检查它是否在您的群集上运行:

kubectl get services kube-dns --namespace=kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.0.0.10    <none>        53/UDP,53/TCP   8m

If it isn’t running, you can enable it. The rest of this section will assume you have a Service with a long lived IP (my-nginx), and a DNS server that has assigned a name to that IP (the CoreDNS cluster addon), so you can talk to the Service from any pod in your cluster using standard methods (e.g. gethostbyname). Let’s run another curl application to test this: 如果它没有运行,您可以启用它。本节的其余部分将假设您有一个具有长寿命IP(My Nginx)的服务,以及一个为该IP分配了名称的DNS服务器(CoreDNS群集加载项),因此您可以使用标准方法(例如gethostbyname)从群集中的任何POD与该服务进行对话。让我们运行另一个curl应用程序来测试这一点:

kubectl run curl --image=radial/busyboxplus:curl -i --tty
Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false
Hit enter for command prompt

Then, hit enter and run nslookup my-nginx: 然后,按enter并运行nslookup my nginx:

[ root@curl-131556218-9fnch:/ ]$ nslookup my-nginx
Server:    10.0.0.10
Address 1: 10.0.0.10

Name:      my-nginx
Address 1: 10.0.162.149

Securing the Service

Till now we have only accessed the nginx server from within the cluster. Before exposing the Service to the internet, you want to make sure the communication channel is secure. For this, you will need: 到目前为止,我们只访问了集群内的nginx服务器。在将服务公开到Internet之前,您需要确保通信通道是安全的。为此,您需要:

  • Self signed certificates for https (unless you already have an identity certificate) https的自签名证书(除非您已经有身份证书)
  • An nginx server configured to use the certificates 配置为使用证书的nginx服务器
  • A secret that makes the certificates accessible to pods 使pods可以访问证书的秘密

You can acquire all these from the nginx https example. This requires having go and make tools installed. If you don’t want to install those, then follow the manual steps later. In short: 您可以从nginx https示例中获取所有这些信息。这需要安装go和make工具。如果不想安装,请稍后按照手动步骤进行操作。简而言之:

make keys secret KEY=/tmp/nginx.key CERT=/tmp/nginx.crt SECRET=/tmp/secret.json
kubectl apply -f /tmp/secret.json
secret/nginxsecret created
kubectl get secrets
NAME                  TYPE                                  DATA      AGE
default-token-il9rc   kubernetes.io/service-account-token   1         1d
nginxsecret           Opaque                                2         1m

Following are the manual steps to follow in case you run into problems running make (on windows for example): 以下是在运行make(例如在windows上)时遇到问题时要遵循的手动步骤:

# 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

Use the output from the previous commands to create a yaml file as follows. The base64 encoded value should all be on a single line. 使用前面命令的输出创建一个yaml文件,如下所示。base64编码的值应该都在一行上。

apiVersion: "v1"
kind: "Secret"
metadata:
  name: "nginxsecret"
  namespace: "default"
data:
  nginx.crt: "LS....tLS0K"
  nginx.key: "LS...S0tLS0K"

Now create the secrets using the file: 现在使用文件创建机密:

kubectl apply -f nginxsecrets.yaml
kubectl get secrets
NAME                  TYPE                                  DATA      AGE
default-token-il9rc   kubernetes.io/service-account-token   1         1d
nginxsecret           Opaque                                2         1m

Now modify your nginx replicas to start an https server using the certificate in the secret, and the Service, to expose both ports (80 and 443): 现在,修改nginx副本以使用机密证书启动https服务器,并修改服务以公开两个端口(80和443):

service/networking/nginx-secure-app.yaml Copy service/networking/nginx-secure-app.yaml to clipboard

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

Noteworthy points about the nginx-secure-app manifest 关于nginx安全应用程序清单的注意事项:

  • It contains both Deployment and Service specification in the same file. 它在同一个文件中同时包含部署和服务规范。

  • The nginx server serves HTTP traffic on port 80 and HTTPS traffic on 443, and nginx Service exposes both ports. nginx服务器在端口80上提供http流量,在443上提供https流量,nginx服务公开这两个端口。

  • Each container has access to the keys through a volume mounted at /etc/nginx/ssl. This is setup before the nginx server is started. 每个容器都可以通过安装在/etc/nginx/ssl的卷访问密钥。这是在nginx服务器启动之前设置的。

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

At this point you can reach the nginx server from any node. 此时,您可以从任何节点访问nginx服务器。

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>

Note how we supplied the -k parameter to curl in the last step, this is because we don’t know anything about the pods running nginx at certificate generation time, so we have to tell curl to ignore the CName mismatch. By creating a Service we linked the CName used in the certificate with the actual DNS name used by pods during Service lookup. Let’s test this from a pod (the same secret is being reused for simplicity, the pod only needs nginx.crt to access the Service): 请注意,在最后一步中,我们是如何为curl提供-k参数的,这是因为我们不知道在证书生成时运行nginx的pods的任何信息,所以我们必须告诉curl忽略cname不匹配。通过创建服务,我们将证书中使用的cname与pods在服务查找期间使用的实际dns名称相链接。让我们从一个pod中测试一下(为了简单起见,为了重复使用相同的秘密,pod只需要nginx.crt来访问服务):

service/networking/curlpod.yaml Copy service/networking/curlpod.yaml to clipboard

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
kubectl apply -f ./curlpod.yaml
kubectl get pods -l app=curlpod
NAME                               READY     STATUS    RESTARTS   AGE
curl-deployment-1515033274-1410r   1/1       Running   0          1m
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/nginx.crt
...
<title>Welcome to nginx!</title>
...

Exposing the Service

For some parts of your applications you may want to expose a Service onto an external IP address. Kubernetes supports two ways of doing this: NodePorts and LoadBalancers. The Service created in the last section already used NodePort, so your nginx HTTPS replica is ready to serve traffic on the internet if your node has a public IP. 对于应用程序的某些部分,您可能希望将服务公开到外部IP地址。kubernetes支持两种方法:nodeport和loadbalancer。在上一节中创建的服务已经使用了“nodeport”,因此如果您的节点有一个公共IP,那么您的nginx https副本就可以为Internet上的流量提供服务了。

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
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>

Let’s now recreate the Service to use a cloud load balancer, just change the Type of my-nginx Service from NodePort to LoadBalancer: 现在让我们重新创建使用云负载平衡器的服务,只需将我的nginx服务的类型从nodeport更改为load balancer:

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
curl https://<EXTERNAL-IP> -k
...
<title>Welcome to nginx!</title>

The IP address in the EXTERNAL-IP column is the one that is available on the public internet. The CLUSTER-IP is only available inside your cluster/private cloud network. “外部IP”列中的IP地址是公共Internet上可用的地址。cluster-ip仅在集群/私有云网络中可用。

Note that on AWS, type LoadBalancer creates an ELB, which uses a (long) hostname, not an IP. It’s too long to fit in the standard kubectl get svc output, in fact, so you’ll need to do kubectl describe service my-nginx to see it. You’ll see something like this: 注意,在aws上,类型loadbalancer创建一个elb,它使用(长)主机名,而不是ip。事实上,kubectl get svc的标准输出太长,所以您需要执行kubectl descripe service my nginx才能看到它。你会看到这样的东西:

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

What's next

Kubernetes also supports Federated Services, which can span multiple clusters and cloud providers, to provide increased availability, better fault tolerance and greater scalability for your services. See the Federated Services User Guide for further information. kubernetes还支持联邦服务,它可以跨越多个集群和云提供商,为您的服务提供更高的可用性、更好的容错性和更大的可伸缩性。有关更多信息,请参阅《联邦服务用户指南》。

Feedback

Was this page helpful?

k8s
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!