Convert site to Hugo (#8316)
This commit converts content and layout to use Hugo.
This commit is contained in:
committed by
k8s-ci-robot
parent
7745f0e0c5
commit
7f3b633aa0
+75
@@ -0,0 +1,75 @@
|
||||
---
|
||||
approvers:
|
||||
- rickypai
|
||||
- thockin
|
||||
title: 使用 HostAliases 向 Pod /etc/hosts 文件添加条目
|
||||
redirect_from:
|
||||
- "/docs/user-guide/add-entries-to-pod-etc-hosts-with-host-aliases/"
|
||||
- "/docs/user-guide/add-entries-to-pod-etc-hosts-with-host-aliases.md"
|
||||
---
|
||||
|
||||
{{< toc >}}
|
||||
|
||||
|
||||
|
||||
当 DNS 配置以及其它选项不合理的时候,通过向 Pod 的 /etc/hosts 文件中添加条目,可以在 Pod 级别覆盖对主机名的解析。在 1.7 版本,用户可以通过 PodSpec 的 HostAliases 字段来添加这些自定义的条目。
|
||||
|
||||
建议通过使用 HostAliases 来进行修改,因为该文件由 Kubelet 管理,并且可以在 Pod 创建/重启过程中被重写。
|
||||
|
||||
|
||||
|
||||
## 默认 hosts 文件内容
|
||||
|
||||
让我们从一个 Nginx Pod 开始,给该 Pod 分配一个 IP:
|
||||
|
||||
```
|
||||
$ kubectl get pods --output=wide
|
||||
NAME READY STATUS RESTARTS AGE IP NODE
|
||||
nginx 1/1 Running 0 13s 10.200.0.4 worker0
|
||||
```
|
||||
|
||||
|
||||
|
||||
默认,hosts 文件只包含 ipv4 和 ipv6 的样板内容,像 `localhost` 和主机名称。
|
||||
|
||||
## 通过 HostAliases 增加额外的条目
|
||||
|
||||
|
||||
|
||||
除了默认的样板内容,我们可以向 hosts 文件添加额外的条目,将 `foo.local`、 `bar.local` 解析为`127.0.0.1`,将 `foo.remote`、 `bar.remote` 解析为 `10.1.2.3`,我们可以在 `.spec.hostAliases` 下为 Pod 添加 HostAliases。
|
||||
|
||||
{{< code file="hostaliases-pod.yaml" >}}
|
||||
|
||||
hosts 文件的内容看起来类似如下这样:
|
||||
|
||||
```
|
||||
$ kubectl logs hostaliases-pod
|
||||
# Kubernetes-managed hosts file.
|
||||
127.0.0.1 localhost
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
fe00::0 ip6-localnet
|
||||
fe00::0 ip6-mcastprefix
|
||||
fe00::1 ip6-allnodes
|
||||
fe00::2 ip6-allrouters
|
||||
10.200.0.4 hostaliases-pod
|
||||
127.0.0.1 foo.local
|
||||
127.0.0.1 bar.local
|
||||
10.1.2.3 foo.remote
|
||||
10.1.2.3 bar.remote
|
||||
```
|
||||
|
||||
|
||||
|
||||
在最下面额外添加了一些条目。
|
||||
|
||||
## 限制
|
||||
|
||||
在 1.7 版本,如果 Pod 启用 hostNetwork,那么将不能使用这个特性,因为 kubelet 只管理非 hostNetwork 类型 Pod 的 hosts 文件。目前正在讨论要改变这个情况。
|
||||
|
||||
|
||||
|
||||
## 为什么 Kubelet 管理 hosts文件?
|
||||
|
||||
kubelet [管理](https://github.com/kubernetes/kubernetes/issues/14633) Pod 中每个容器的 hosts 文件,避免 Docker 在容器已经启动之后去 [修改](https://github.com/moby/moby/issues/17190) 该文件。
|
||||
|
||||
因为该文件是托管性质的文件,无论容器重启或 Pod 重新调度,用户修改该 hosts 文件的任何内容,都会在 Kubelet 重新安装后被覆盖。因此,不建议修改该文件的内容。
|
||||
@@ -0,0 +1,371 @@
|
||||
---
|
||||
approvers:
|
||||
- caesarxuchao
|
||||
- lavalamp
|
||||
- thockin
|
||||
title: 应用连接到 Service
|
||||
---
|
||||
|
||||
{{< toc >}}
|
||||
|
||||
|
||||
|
||||
## Kubernetes 连接容器模型
|
||||
|
||||
既然有了一个持续运行、可复制的应用,我们就能够将它暴露到网络上。
|
||||
在讨论 Kubernetes 网络连接的方式之前,非常值得与 Docker 中 “正常” 方式的网络进行对比。
|
||||
|
||||
|
||||
|
||||
默认情况下,Docker 使用私有主机网络连接,只能与同在一台机器上的容器进行通信。
|
||||
为了实现容器的跨节点通信,必须在机器自己的 IP 上为这些容器分配端口,为容器进行端口转发或者代理。
|
||||
|
||||
多个开发人员之间协调端口的使用很难做到规模化,那些难以控制的集群级别的问题,都会交由用户自己去处理。
|
||||
Kubernetes 假设 Pod 可与其它 Pod 通信,不管它们在哪个主机上。
|
||||
我们给 Pod 分配属于自己的集群私有 IP 地址,所以没必要在 Pod 或映射到的容器的端口和主机端口之间显式地创建连接。
|
||||
这表明了在 Pod 内的容器都能够连接到本地的每个端口,集群中的所有 Pod 不需要通过 NAT 转换就能够互相看到。
|
||||
文档的剩余部分将详述如何在一个网络模型之上运行可靠的服务。
|
||||
|
||||
该指南使用一个简单的 Nginx server 来演示并证明谈到的概念。同样的原则也体现在一个更加完整的 [Jenkins CI 应用](http://blog.kubernetes.io/2015/07/strong-simple-ssl-for-kubernetes.html) 中。
|
||||
|
||||
|
||||
|
||||
## 在集群中暴露 Pod
|
||||
|
||||
我们在之前的示例中已经做过,然而再让我重试一次,这次聚焦在网络连接的视角。
|
||||
创建一个 Nginx Pod,指示它具有一个容器端口的说明:
|
||||
|
||||
{{< code file="run-my-nginx.yaml" >}}
|
||||
|
||||
|
||||
|
||||
这使得可以从集群中任何一个节点来访问它。检查节点,该 Pod 正在运行:
|
||||
|
||||
```shell
|
||||
$ kubectl create -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
|
||||
```
|
||||
|
||||
|
||||
|
||||
检查 Pod 的 IP 地址:
|
||||
|
||||
```shell
|
||||
$ kubectl get pods -l run=my-nginx -o yaml | grep podIP
|
||||
podIP: 10.244.3.4
|
||||
podIP: 10.244.2.5
|
||||
```
|
||||
|
||||
|
||||
|
||||
应该能够通过 ssh 登录到集群中的任何一个节点上,使用 curl 也能调通所有 IP 地址。
|
||||
需要注意的是,容器不会使用该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。
|
||||
这意味着可以在同一个节点上运行多个 Pod,使用相同的容器端口,并且可以从集群中任何其他的 Pod 或节点上使用 IP 的方式访问到它们。
|
||||
像 Docker 一样,端口能够被发布到主机节点的接口上,但是出于网络模型的原因应该从根本上减少这种用法。
|
||||
|
||||
如果对此好奇,可以获取更多关于 [如何实现网络模型](/docs/concepts/cluster-administration/networking/#how-to-achieve-this) 的内容。
|
||||
|
||||
|
||||
|
||||
## 创建 Service
|
||||
|
||||
我们有 Pod 在一个扁平的、集群范围的地址空间中运行 Nginx 服务,可以直接连接到这些 Pod,但如果某个节点死掉了会发生什么呢?
|
||||
Pod 会终止,Deployment 将创建新的 Pod,且使用不同的 IP。这正是 Service 要解决的问题。
|
||||
|
||||
Kubernetes Service 从逻辑上定义了运行在集群中的一组 Pod,这些 Pod 提供了相同的功能。
|
||||
当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP)。
|
||||
这个 IP 地址与一个 Service 的生命周期绑定在一起,当 Service 存在的时候它也不会改变。
|
||||
可以配置 Pod 使它与 Service 进行通信,Pod 知道与 Service 通信将被自动地负载均衡到该 Service 中的某些 Pod 上。
|
||||
|
||||
可以使用 `kubectl expose` 命令为 2个 Nginx 副本创建一个 Service:
|
||||
|
||||
```shell
|
||||
$ kubectl expose deployment/my-nginx
|
||||
service "my-nginx" exposed
|
||||
```
|
||||
|
||||
|
||||
|
||||
这等价于使用 `kubectl create -f` 命令创建,对应如下的 yaml 文件:
|
||||
|
||||
{{< code file="nginx-svc.yaml" >}}
|
||||
|
||||
|
||||
|
||||
上述规约将创建一个 Service,对应具有标签 `run: my-nginx` 的 Pod,目标 TCP 端口 80,并且在一个抽象的 Service 端口(`targetPort`:容器接收流量的端口;`port`:抽象的 Service 端口,可以使任何其它 Pod 访问该 Service 的端口)上暴露。
|
||||
查看 [Service API 对象](/docs/api-reference/{{< param "version" >}}/#service-v1-core) 了解 Service 定义支持的字段列表。
|
||||
|
||||
```shell
|
||||
$ kubectl get svc my-nginx
|
||||
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
my-nginx 10.0.162.149 <none> 80/TCP 21s
|
||||
```
|
||||
|
||||
|
||||
|
||||
正如前面所提到的,一个 Service 由一组 backend Pod 组成。这些 Pod 通过 `endpoints` 暴露出来。
|
||||
Service Selector 将持续评估,结果被 POST 到一个名称为 `my-nginx` 的 Endpoint 对象上。
|
||||
当 Pod 终止后,它会自动从 Endpoint 中移除,新的能够匹配上 Service Selector 的 Pod 将自动地被添加到 Endpoint 中。
|
||||
检查该 Endpoint,注意到 IP 地址与在第一步创建的 Pod 是相同的。
|
||||
|
||||
```shell
|
||||
$ kubectl describe svc my-nginx
|
||||
Name: my-nginx
|
||||
Namespace: default
|
||||
Labels: run=my-nginx
|
||||
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
|
||||
No events.
|
||||
|
||||
$ kubectl get ep my-nginx
|
||||
NAME ENDPOINTS AGE
|
||||
my-nginx 10.244.2.5:80,10.244.3.4:80 1m
|
||||
```
|
||||
|
||||
|
||||
|
||||
现在,能够从集群中任意节点上使用 curl 命令请求 Nginx Service `<CLUSTER-IP>:<PORT>` 。
|
||||
注意 Service IP 完全是虚拟的,它从来没有走过网络,如果对它如何工作的原理感到好奇,可以阅读更多关于 [服务代理](/docs/user-guide/services/#virtual-ips-and-service-proxies) 的内容。
|
||||
|
||||
|
||||
|
||||
## 访问 Service
|
||||
|
||||
Kubernetes 支持两种主要的服务发现模式 —— 环境变量和 DNS。前者在单个节点上可用使用,然而后者必须使用 [kube-dns 集群插件](http://releases.k8s.io/{{< param "githubbranch" >}}/cluster/addons/dns/README.md)。
|
||||
|
||||
|
||||
|
||||
### 环境变量
|
||||
|
||||
当 Pod 在 Node 上运行时,kubelet 会为每个活跃的 Service 添加一组环境变量。这会有一个顺序的问题。想了解为何,检查正在运行的 Nginx Pod 的环境变量(Pod 名称将不会相同):
|
||||
|
||||
```shell
|
||||
$ 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
|
||||
```
|
||||
|
||||
|
||||
|
||||
注意,还没有谈及到 Service。这是因为创建副本先于 Service。
|
||||
这样做的另一个缺点是,调度器可能在同一个机器上放置所有 Pod,如果该机器宕机则所有的 Service 都会挂掉。
|
||||
正确的做法是,我们杀掉 2 个 Pod,等待 Deployment 去创建它们。
|
||||
这次 Service 会 *先于* 副本存在。这将实现调度器级别的 Service,能够使 Pod 分散创建(假定所有的 Node 都具有同样的容量),以及正确的环境变量:
|
||||
|
||||
```shell
|
||||
$ 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
|
||||
```
|
||||
|
||||
|
||||
|
||||
可能注意到,Pod 具有不同的名称,因为它们被杀掉后并被重新创建。
|
||||
|
||||
```shell
|
||||
$ 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 提供了一个 DNS 插件 Service,它使用 skydns 自动为其它 Service 指派 DNS 名字。
|
||||
如果它在集群中处于运行状态,可以通过如下命令来检查:
|
||||
|
||||
```shell
|
||||
$ kubectl get services kube-dns --namespace=kube-system
|
||||
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
kube-dns 10.0.0.10 <none> 53/UDP,53/TCP 8m
|
||||
```
|
||||
|
||||
|
||||
|
||||
如果没有在运行,可以 [启用它](http://releases.k8s.io/{{< param "githubbranch" >}}/cluster/addons/dns/README.md#how-do-i-configure-it)。
|
||||
本段剩余的内容,将假设已经有一个 Service,它具有一个长久存在的 IP(my-nginx),一个为该 IP 指派名称的 DNS 服务器(kube-dns 集群插件),所以可以通过标准做法,使在集群中的任何 Pod 都能与该 Service 通信(例如:gethostbyname)。
|
||||
让我们运行另一个 curl 应用来进行测试:
|
||||
|
||||
```shell
|
||||
$ 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
|
||||
```
|
||||
|
||||
|
||||
然后,按回车并执行命令 `nslookup my-nginx`:
|
||||
|
||||
```shell
|
||||
[ 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
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Service 安全
|
||||
|
||||
到现在为止,我们只在集群内部访问了 Nginx server。在将 Service 暴露到 Internet 之前,我们希望确保通信信道是安全的。对于这可能需要:
|
||||
|
||||
|
||||
|
||||
* https 自签名证书(除非已经有了一个识别身份的证书)
|
||||
* 使用证书配置的 Nginx server
|
||||
* 使证书可以访问 Pod 的[秘钥](/docs/user-guide/secrets)
|
||||
|
||||
可以从 [Nginx https 示例](https://github.com/kubernetes/kubernetes/tree/{{< param "githubbranch" >}}/examples/https-nginx/) 获取所有上述内容,简明示例如下:
|
||||
|
||||
```shell
|
||||
$ make keys secret KEY=/tmp/nginx.key CERT=/tmp/nginx.crt SECRET=/tmp/secret.json
|
||||
$ kubectl create -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
|
||||
```
|
||||
|
||||
|
||||
|
||||
现在修改 Nginx 副本,启动一个使用在秘钥中的证书的 https 服务器和 Servcie,都暴露端口(80 和 443):
|
||||
|
||||
{{< code file="nginx-secure-app.yaml" >}}
|
||||
|
||||
|
||||
|
||||
关于 nginx-secure-app manifest 值得注意的点如下:
|
||||
|
||||
|
||||
|
||||
- 它在相同的文件中包含了 Deployment 和 Service 的规格
|
||||
- [Nginx server](https://github.com/kubernetes/kubernetes/tree/{{< param "githubbranch" >}}/examples/https-nginx/default.conf) 处理 80 端口上的 http 流量,以及 443 端口上的 https 流量,Nginx Service 暴露了这两个端口。
|
||||
- 每个容器访问挂载在 /etc/nginx/ssl 卷上的秘钥。这需要在 Nginx server 启动之前安装好。
|
||||
|
||||
```shell
|
||||
$ kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml
|
||||
```
|
||||
|
||||
|
||||
|
||||
这时可以从任何节点访问到 Nginx server。
|
||||
|
||||
```shell
|
||||
$ 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>
|
||||
```
|
||||
|
||||
|
||||
|
||||
注意最后一步我们是如何提供 `-k` 参数执行 curl命令的,这是因为在证书生成时,我们不知道任何关于运行 Nginx 的 Pod 的信息,所以不得不在执行 curl 命令时忽略 CName 不匹配的情况。
|
||||
通过创建 Service,我们连接了在证书中的 CName 与在 Service 查询时被 Pod使用的实际 DNS 名字。
|
||||
让我们从一个 Pod 来测试(为了简化使用同一个秘钥,Pod 仅需要使用 nginx.crt 去访问 Service):
|
||||
|
||||
{{< code file="curlpod.yaml" >}}
|
||||
|
||||
```shell
|
||||
$ kubectl create -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>
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## 暴露 Service
|
||||
|
||||
对我们应用的某些部分,可能希望将 Service 暴露在一个外部 IP 地址上。
|
||||
Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。
|
||||
在上一段创建的 Service 使用了 `NodePort`,因此 Nginx https 副本已经就绪,如果使用一个公网 IP,能够处理 Internet 上的流量。
|
||||
|
||||
```shell
|
||||
$ 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>
|
||||
```
|
||||
|
||||
|
||||
|
||||
让我们重新创建一个 Service,使用一个云负载均衡器,只需要将 `my-nginx` Service 的 `Type` 由 `NodePort` 改成 `LoadBalancer`。
|
||||
|
||||
```shell
|
||||
$ kubectl edit svc my-nginx
|
||||
$ kubectl get svc my-nginx
|
||||
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
my-nginx 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>
|
||||
```
|
||||
|
||||
|
||||
|
||||
在 `EXTERNAL-IP` 列指定的 IP 地址是在公网上可用的。`CLUSTER-IP` 只在集群/私有云网络中可用。
|
||||
|
||||
注意,在 AWS 上类型 `LoadBalancer` 创建一个 ELB,它使用主机名(比较长),而不是 IP。
|
||||
它太长以至于不能适配标准 `kubectl get svc` 的输出,事实上需要通过执行 `kubectl describe service my-nginx` 命令来查看它。
|
||||
可以看到类似如下内容:
|
||||
|
||||
```shell
|
||||
$ kubectl describe service my-nginx
|
||||
...
|
||||
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
|
||||
...
|
||||
```
|
||||
|
||||
## 进一步阅读
|
||||
|
||||
Kubernetes 也支持联合 Service,能够跨多个集群和云提供商,为 Service 提供逐步增强的可用性、更优的容错、更好的可伸缩性。
|
||||
查看 [联合 Service 用户指南](/docs/concepts/cluster-administration/federation-service-discovery/) 获取更进一步信息。
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: curl-deployment
|
||||
spec:
|
||||
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
|
||||
@@ -0,0 +1,419 @@
|
||||
---
|
||||
approvers:
|
||||
- davidopp
|
||||
- thockin
|
||||
title: DNS Pod 与 Service
|
||||
redirect_from:
|
||||
- "/docs/admin/dns/"
|
||||
- "/docs/admin/dns.html"
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 介绍
|
||||
|
||||
Kubernetes 从 1.3 版本起, DNS 是内置的服务,通过插件管理器 [集群插件](http://releases.k8s.io/{{< param "githubbranch" >}}/cluster/addons/README.md) 自动被启动。
|
||||
|
||||
Kubernetes DNS 在集群中调度 DNS Pod 和 Service ,配置 kubelet 以通知个别容器使用 DNS Service 的 IP 解析 DNS 名字。
|
||||
|
||||
|
||||
|
||||
|
||||
## 怎样获取 DNS 名字?
|
||||
|
||||
在集群中定义的每个 Service(包括 DNS 服务器自身)都会被指派一个 DNS 名称。
|
||||
默认,一个客户端 Pod 的 DNS 搜索列表将包含该 Pod 自己的 Namespace 和集群默认域。
|
||||
通过如下示例可以很好地说明:
|
||||
|
||||
假设在 Kubernetes 集群的 Namespace `bar` 中,定义了一个Service `foo`。
|
||||
运行在Namespace `bar` 中的一个 Pod,可以简单地通过 DNS 查询 `foo` 来找到该 Service。
|
||||
运行在 Namespace `quux` 中的一个 Pod 可以通过 DNS 查询 `foo.bar` 找到该 Service。
|
||||
|
||||
|
||||
|
||||
## 支持的 DNS 模式
|
||||
|
||||
下面各段详细说明支持的记录类型和布局。
|
||||
如果任何其它的布局、名称或查询,碰巧也能够使用,这就需要研究下它们的实现细节,以免后续修改它们又不能使用了。
|
||||
|
||||
|
||||
|
||||
### Service
|
||||
|
||||
#### A 记录
|
||||
|
||||
“正常” Service(除了 Headless Service)会以 `my-svc.my-namespace.svc.cluster.local` 这种名字的形式被指派一个 DNS A 记录。这会解析成该 Service 的 Cluster IP。
|
||||
|
||||
“Headless” Service(没有Cluster IP)也会以 `my-svc.my-namespace.svc.cluster.local` 这种名字的形式被指派一个 DNS A 记录。
|
||||
不像正常 Service,它会解析成该 Service 选择的一组 Pod 的 IP。
|
||||
希望客户端能够使用这一组 IP,否则就使用标准的 round-robin 策略从这一组 IP 中进行选择。
|
||||
|
||||
|
||||
|
||||
#### SRV 记录
|
||||
|
||||
命名端口需要创建 SRV 记录,这些端口是正常 Service或 [Headless
|
||||
Services](/docs/concepts/services-networking/service/#headless-services) 的一部分。
|
||||
对每个命名端口,SRV 记录具有 `_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local` 这种形式。
|
||||
对普通 Service,这会被解析成端口号和 CNAME:`my-svc.my-namespace.svc.cluster.local`。
|
||||
对 Headless Service,这会被解析成多个结果,Service 对应的每个 backend Pod 各一个,包含 `auto-generated-name.my-svc.my-namespace.svc.cluster.local` 这种形式 Pod 的端口号和 CNAME。
|
||||
|
||||
#### 后向兼容性
|
||||
|
||||
上一版本的 kube-dns 使用 `my-svc.my-namespace.cluster.local` 这种形式的名字(后续会增加 'svc' 这一级),以后这将不再被支持。
|
||||
|
||||
|
||||
|
||||
### Pod
|
||||
|
||||
#### A 记录
|
||||
|
||||
如果启用,Pod 会以 `pod-ip-address.my-namespace.pod.cluster.local` 这种形式被指派一个 DNS A 记录。
|
||||
|
||||
例如,`default` Namespace 具有 DNS 名字 `cluster.local`,在该 Namespace 中一个 IP 为 `1.2.3.4` 的 Pod 将具有一个条目:`1-2-3-4.default.pod.cluster.local`。
|
||||
|
||||
|
||||
|
||||
#### 基于 Pod hostname、subdomain 字段的 A 记录和主机名
|
||||
|
||||
当前,创建 Pod 后,它的主机名是该 Pod 的 `metadata.name` 值。
|
||||
|
||||
在 v1.2 版本中,用户可以配置 Pod annotation, 通过 `pod.beta.kubernetes.io/hostname` 来设置 Pod 的主机名。
|
||||
如果为 Pod 配置了 annotation,会优先使用 Pod 的名称作为主机名。
|
||||
例如,给定一个 Pod,它具有 annotation `pod.beta.kubernetes.io/hostname: my-pod-name`,该 Pod 的主机名被设置为 “my-pod-name”。
|
||||
|
||||
|
||||
|
||||
在 v1.3 版本中,PodSpec 具有 `hostname` 字段,可以用来指定 Pod 的主机名。这个字段的值优先于 annotation `pod.beta.kubernetes.io/hostname`。
|
||||
在 v1.2 版本中引入了 beta 特性,用户可以为 Pod 指定 annotation,其中 `pod.beta.kubernetes.io/subdomain` 指定了 Pod 的子域名。
|
||||
最终的域名将是 “<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>”。
|
||||
举个例子,Pod 的主机名 annotation 设置为 “foo”,子域名 annotation 设置为 “bar”,在 Namespace “my-namespace” 中对应的 FQDN 为 “foo.bar.my-namespace.svc.cluster.local”。
|
||||
|
||||
|
||||
|
||||
在 v1.3 版本中,PodSpec 具有 `subdomain` 字段,可以用来指定 Pod 的子域名。
|
||||
这个字段的值优先于 annotation `pod.beta.kubernetes.io/subdomain` 的值。
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: default-subdomain
|
||||
spec:
|
||||
selector:
|
||||
name: busybox
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: foo # Actually, no port is needed.
|
||||
port: 1234
|
||||
targetPort: 1234
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: busybox1
|
||||
labels:
|
||||
name: busybox
|
||||
spec:
|
||||
hostname: busybox-1
|
||||
subdomain: default-subdomain
|
||||
containers:
|
||||
- image: busybox
|
||||
command:
|
||||
- sleep
|
||||
- "3600"
|
||||
name: busybox
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: busybox2
|
||||
labels:
|
||||
name: busybox
|
||||
spec:
|
||||
hostname: busybox-2
|
||||
subdomain: default-subdomain
|
||||
containers:
|
||||
- image: busybox
|
||||
command:
|
||||
- sleep
|
||||
- "3600"
|
||||
name: busybox
|
||||
```
|
||||
|
||||
|
||||
|
||||
如果 Headless Service 与 Pod 在同一个 Namespace 中,它们具有相同的子域名,集群的 KubeDNS 服务器也会为该 Pod 的完整合法主机名返回 A 记录。
|
||||
在同一个 Namespace 中,给定一个主机名为 “busybox-1” 的 Pod,子域名设置为 “default-subdomain”,名称为 “default-subdomain” 的 Headless Service ,Pod 将看到自己的 FQDN 为 “busybox-1.default-subdomain.my-namespace.svc.cluster.local”。
|
||||
DNS 会为那个名字提供一个 A 记录,指向该 Pod 的 IP。
|
||||
“busybox1” 和 “busybox2” 这两个 Pod 分别具有它们自己的 A 记录。
|
||||
|
||||
|
||||
|
||||
在Kubernetes v1.2 版本中,`Endpoints` 对象也具有 annotation `endpoints.beta.kubernetes.io/hostnames-map`。
|
||||
它的值是 map[string(IP)][endpoints.HostRecord] 的 JSON 格式,例如: '{"10.245.1.6":{HostName: "my-webserver"}}'。
|
||||
|
||||
如果是 Headless Service 的 `Endpoints`,会以 <hostname>.<service name>.<pod namespace>.svc.<cluster domain> 的格式创建 A 记录。
|
||||
对示例中的 JSON 字符串,如果 `Endpoints` 是为名称为 “bar” 的 Headless Service 而创建的,其中一个 `Endpoints` 的 IP 是 “10.245.1.6”,则会创建一个名称为 “my-webserver.bar.my-namespace.svc.cluster.local” 的 A 记录,该 A 记录查询将返回 “10.245.1.6”。
|
||||
|
||||
`Endpoints` annotation 通常没必要由最终用户指定,但可以被内部的 Service Controller 用来提供上述功能。
|
||||
|
||||
|
||||
|
||||
在 v1.3 版本中,`Endpoints` 对象可以为任何 endpoint 指定 `hostname` 和 IP。
|
||||
`hostname` 字段优先于通过 `endpoints.beta.kubernetes.io/hostnames-map` annotation 指定的主机名。
|
||||
|
||||
在 v1.3 版本中,下面的 annotation 是过时的:`pod.beta.kubernetes.io/hostname`、`pod.beta.kubernetes.io/subdomain`、`endpoints.beta.kubernetes.io/hostnames-map`。
|
||||
|
||||
|
||||
|
||||
## 如何测试它是否可以使用?
|
||||
|
||||
### 创建一个简单的 Pod 作为测试环境
|
||||
|
||||
创建 `busybox.yaml` 文件,内容如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: busybox
|
||||
namespace: default
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox
|
||||
command:
|
||||
- sleep
|
||||
- "3600"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: busybox
|
||||
restartPolicy: Always
|
||||
```
|
||||
|
||||
|
||||
|
||||
然后,用该文件创建一个 Pod:
|
||||
|
||||
```
|
||||
kubectl create -f busybox.yaml
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 等待这个 Pod 变成运行状态
|
||||
|
||||
获取它的状态,执行如下命令:
|
||||
|
||||
```
|
||||
kubectl get pods busybox
|
||||
```
|
||||
|
||||
|
||||
|
||||
可以看到如下内容:
|
||||
|
||||
```
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
busybox 1/1 Running 0 <some-time>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 验证 DNS 已经生效
|
||||
|
||||
一旦 Pod 处于运行中状态,可以在测试环境中执行如下 nslookup 查询:
|
||||
|
||||
```
|
||||
kubectl exec -ti busybox -- nslookup kubernetes.default
|
||||
```
|
||||
|
||||
|
||||
|
||||
可以看到类似如下的内容:
|
||||
|
||||
```
|
||||
Server: 10.0.0.10
|
||||
Address 1: 10.0.0.10
|
||||
|
||||
Name: kubernetes.default
|
||||
Address 1: 10.0.0.1
|
||||
```
|
||||
|
||||
|
||||
|
||||
如果看到了,说明 DNS 已经可以正确工作了。
|
||||
|
||||
### 问题排查技巧
|
||||
|
||||
如果执行 nslookup 命令失败,检查如下内容:
|
||||
|
||||
#### 先检查本地 DNS 配置
|
||||
|
||||
查看配置文件 resolv.conf。(关于更多信息,参考下面的 “从 Node 继承 DNS” 和 “已知问题”。)
|
||||
|
||||
```
|
||||
kubectl exec busybox cat /etc/resolv.conf
|
||||
```
|
||||
|
||||
|
||||
|
||||
按照如下方法(注意搜索路径可能会因为云提供商不同而变化)验证搜索路径和 Name Server 的建立:
|
||||
|
||||
```
|
||||
search default.svc.cluster.local svc.cluster.local cluster.local google.internal c.gce_project_id.internal
|
||||
nameserver 10.0.0.10
|
||||
options ndots:5
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 快速诊断
|
||||
|
||||
出现类似如下指示的错误,说明 kube-dns 插件或相关 Service 存在问题:
|
||||
|
||||
```
|
||||
$ kubectl exec -ti busybox -- nslookup kubernetes.default
|
||||
Server: 10.0.0.10
|
||||
Address 1: 10.0.0.10
|
||||
|
||||
nslookup: can't resolve 'kubernetes.default'
|
||||
```
|
||||
|
||||
|
||||
|
||||
或者
|
||||
|
||||
```
|
||||
$ kubectl exec -ti busybox -- nslookup kubernetes.default
|
||||
Server: 10.0.0.10
|
||||
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
|
||||
|
||||
nslookup: can't resolve 'kubernetes.default'
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 检查是否 DNS Pod 正在运行
|
||||
|
||||
使用 `kubectl get pods` 命令验证 DNS Pod 正在运行:
|
||||
|
||||
```
|
||||
kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
|
||||
```
|
||||
|
||||
|
||||
|
||||
应该能够看到类似如下信息:
|
||||
|
||||
```
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
...
|
||||
kube-dns-v19-ezo1y 3/3 Running 0 1h
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
||||
如果看到没有 Pod 运行,或 Pod 失败/结束,DNS 插件不能默认部署到当前的环境,必须手动部署。
|
||||
|
||||
#### 检查 DNS Pod 中的错误信息
|
||||
|
||||
使用 `kubectl logs` 命令查看 DNS 后台进程的日志:
|
||||
|
||||
```
|
||||
kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c kubedns
|
||||
kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c dnsmasq
|
||||
kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c healthz
|
||||
```
|
||||
|
||||
|
||||
|
||||
查看是否有任何可疑的日志。在行开头的字母 W、E、F 分别表示 警告、错误、失败。请搜索具有这些日志级别的日志行,通过 [Kubernetes 问题](https://github.com/kubernetes/kubernetes/issues) 报告意外的错误。
|
||||
|
||||
#### DNS 服务是否运行?
|
||||
|
||||
通过使用 `kubectl get service` 命令,验证 DNS 服务是否运行:
|
||||
|
||||
```
|
||||
kubectl get svc --namespace=kube-system
|
||||
```
|
||||
|
||||
|
||||
|
||||
应该能够看到:
|
||||
|
||||
```
|
||||
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
...
|
||||
kube-dns 10.0.0.10 <none> 53/UDP,53/TCP 1h
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
||||
如果服务已经创建,或在这个例子中默认被创建,但是并没有看到,可以查看 [调试 Service 页面](/docs/tasks/debug-application-cluster/debug-service/) 获取更多信息。
|
||||
|
||||
```
|
||||
kubectl get ep kube-dns --namespace=kube-system
|
||||
```
|
||||
|
||||
|
||||
|
||||
应该能够看到类似如下信息:
|
||||
|
||||
```
|
||||
NAME ENDPOINTS AGE
|
||||
kube-dns 10.180.3.17:53,10.180.3.17:53 1h
|
||||
```
|
||||
|
||||
|
||||
|
||||
如果没有看到 Endpoint,查看 [调试 Service 文档](/docs/tasks/debug-application-cluster/debug-service/) 中的 Endpoint 段内容。
|
||||
|
||||
关于更多 Kubernetes DNS 的示例,参考 Kubernetes GitHub 仓库中 [集群 DNS 示例](https://git.k8s.io/kubernetes/examples/cluster-dns)。
|
||||
|
||||
## Kubernetes Federation(多 Zone 支持)
|
||||
|
||||
在1.3 发行版本中,为多站点 Kubernetes 安装引入了集群 Federation 支持。这需要对 Kubernetes 集群 DNS 服务器处理 DNS 查询的方式,做出一些微小(后向兼容)改变,从而便利了对联合 Service 的查询(跨多个 Kubernetes 集群)。参考 [集群 Federation 管理员指南](/docs/concepts/cluster-administration/federation/) 获取更多关于集群 Federation 和多站点支持的细节。
|
||||
|
||||
|
||||
|
||||
## 工作原理
|
||||
|
||||
运行的 Kubernetes DNS Pod 包含 3 个容器 —— kubedns、dnsmasq 和负责健康检查的 healthz。
|
||||
kubedns 进程监视 Kubernetes master 对 Service 和 Endpoint 操作的变更,并维护一个内存查询结构去处理 DNS 请求。dnsmasq 容器增加了一个 DNS 缓存来改善性能。为执行对 dnsmasq 和 kubedns 的健康检查,healthz 容器提供了一个单独的健康检查 Endpoint。
|
||||
|
||||
DNS Pod 通过一个静态 IP 暴露为一个 Service。一旦 IP 被分配,kubelet 会通过 `--cluster-dns=10.0.0.10` 标志将配置的 DNS 传递给每一个容器。
|
||||
|
||||
DNS 名字也需要域名,本地域名是可配置的,在 kubelet 中使用 `--cluster-domain=<default local domain>` 标志。
|
||||
|
||||
Kubernetes 集群 DNS 服务器(根据 [SkyDNS](https://github.com/skynetservices/skydns) 库)支持正向查询(A 记录),Service 查询(SRV 记录)和反向 IP 地址查询(PTR 记录)。
|
||||
|
||||
|
||||
|
||||
## 从 Node 继承 DNS
|
||||
当运行 Pod 时,kubelet 将集群 DNS 服务器和搜索路径追加到 Node 自己的 DNS 设置中。如果 Node 能够在大型环境中解析 DNS 名字,Pod 也应该没问题。参考下面 "已知问题” 中给出的更多说明。
|
||||
|
||||
如果不想这样,或者希望 Pod 有一个不同的 DNS 配置,可以使用 kubelet 的 `--resolv-conf` 标志。设置为 "" 表示 Pod 将不继承自 DNS。设置为一个合法的文件路径,表示 kubelet 将使用这个文件而不是 `/etc/resolv.conf` 。
|
||||
|
||||
|
||||
|
||||
## 已知问题
|
||||
|
||||
Kubernetes 安装但并不配置 Node 的 resolv.conf 文件,而是默认使用集群 DNS的,因为那个过程本质上就是和特定的发行版本相关的。最终应该会被实现。
|
||||
|
||||
Linux libc 在限制为3个 DNS `nameserver` 记录和3个 DNS `search` 记录是不可能卡住的([查看 2005 年的一个 Bug](https://bugzilla.redhat.com/show_bug.cgi?id=168253))。Kubernetes 需要使用1个 `nameserver` 记录和3个 `search` 记录。这意味着如果本地安装已经使用了3个 `nameserver` 或使用了3个以上 `search`,那些设置将会丢失。作为部分解决方法, Node 可以运行 `dnsmasq` ,它能提供更多 `nameserver` 条目,但不能运行更多 `search` 条目。可以使用 kubelet 的 `--resolv-conf` 标志。
|
||||
|
||||
如果使用 3.3 版本的 Alpine 或更早版本作为 base 镜像,由于 Alpine 的一个已知问题,DNS 可能不会正确工作。查看 [这里](https://github.com/kubernetes/kubernetes/issues/30215) 获取更多信息。
|
||||
|
||||
|
||||
|
||||
## 参考
|
||||
|
||||
- [DNS 集群插件文档](http://releases.k8s.io/{{< param "githubbranch" >}}/cluster/addons/dns/README.md)
|
||||
|
||||
|
||||
|
||||
## 下一步
|
||||
|
||||
- [集群中 DNS Service 自动伸缩](/docs/tasks/administer-cluster/dns-horizontal-autoscaling/)
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: hostaliases-pod
|
||||
spec:
|
||||
hostAliases:
|
||||
- ip: "127.0.0.1"
|
||||
hostnames:
|
||||
- "foo.local"
|
||||
- "bar.local"
|
||||
- ip: "10.1.2.3"
|
||||
hostnames:
|
||||
- "foo.remote"
|
||||
- "bar.remote"
|
||||
containers:
|
||||
- name: cat-hosts
|
||||
image: busybox
|
||||
command:
|
||||
- cat
|
||||
args:
|
||||
- "/etc/hosts"
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: test-ingress
|
||||
spec:
|
||||
backend:
|
||||
serviceName: testsvc
|
||||
servicePort: 80
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
approvers:
|
||||
- thockin
|
||||
- caseydavenport
|
||||
- danwinship
|
||||
title: 网络策略
|
||||
---
|
||||
|
||||
{{< toc >}}
|
||||
|
||||
网络策略(NetworkPolicy)是一种关于pod间及pod与其他网络端点间所允许的通信规则的规范。
|
||||
|
||||
`NetworkPolicy` 资源使用标签选择pod,并定义选定pod所允许的通信规则。
|
||||
|
||||
## 前提
|
||||
|
||||
网络策略通过网络插件来实现,所以用户必须使用支持 `NetworkPolicy` 的网络解决方案 - 简单地创建资源对象,而没有控制器来使它生效的话,是没有任何作用的。
|
||||
|
||||
## 隔离和非隔离的Pod
|
||||
|
||||
默认情况下,Pod是非隔离的,它们接受任何来源的流量。
|
||||
|
||||
Pod可以通过相关的网络策略进行隔离。一旦命名空间中有网络策略选择了特定的Pod,该Pod会拒绝网络策略所不允许的连接。 (命名空间下其他未被网络策略所选择的Pod会继续接收所有的流量)
|
||||
|
||||
## `NetworkPolicy` 资源
|
||||
|
||||
通过[api参考](/docs/api-reference/{{< param "version" >}}/#networkpolicy-v1-networking)来了解资源定义。
|
||||
|
||||
下面是一个 `NetworkPolicy` 的示例:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test-network-policy
|
||||
namespace: default
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
role: db
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: myproject
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
role: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
```
|
||||
|
||||
除非选择支持网络策略的网络解决方案,否则将上述示例发送到API服务器没有任何效果。
|
||||
|
||||
__必填字段__: 与所有其他的Kubernetes配置一样,`NetworkPolicy` 需要 `apiVersion`、 `kind`和 `metadata` 字段。 关于配置文件操作的一般信息,请参考 [这里](/docs/user-guide/simple-yaml)、 [这里](/docs/user-guide/configuring-containers)和 [这里](/docs/user-guide/working-with-resources)。
|
||||
|
||||
__spec__: `NetworkPolicy` [spec](https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status) 中包含了在一个命名空间中定义特定网络策略所需的所有信息
|
||||
|
||||
__podSelector__: 每个 `NetworkPolicy` 都包括一个 `podSelector` ,它对该策略所应用的一组Pod进行选择。因为 `NetworkPolicy` 目前只支持定义 `ingress` 规则,这里的 `podSelector` 本质上是为该策略定义 "目标pod" 。示例中的策略选择带有 "role=db" 标签的pod。空的 `podSelector` 选择命名空间下的所有pod。
|
||||
|
||||
__ingress__: 每个 `NetworkPolicy` 包含一个 `ingress` 规则的白名单列表。 (其中的)规则允许同时匹配 `from` 和 `ports` 部分的流量。示例策略中包含一条简单的规则: 它匹配一个单一的端口,来自两个来源中的一个, 第一个通过 `namespaceSelector` 指定,第二个通过 `podSelector` 指定。
|
||||
|
||||
所以,示例网络策略:
|
||||
|
||||
1. 隔离 "default" 命名空间下 "role=db" 的pod (如果它们不是已经被隔离的话)。
|
||||
2. 允许从 "default" 命名空间下带有 "role=frontend" 标签的pod到 "default" 命名空间下的pod的6379 TCP端口的连接。
|
||||
3. 允许从带有 "project=myproject" 标签的命名空间下的任何pod到 "default" 命名空间下的pod的6379 TCP端口的连接。
|
||||
|
||||
查看 [网络策略入门指南](/docs/getting-started-guides/network-policy/walkthrough) 了解更多示例。
|
||||
|
||||
## 默认策略
|
||||
|
||||
用户可以通过创建一个选择所有Pod,但是不允许任何通信的网络策略,来为一个命名空间创建 "默认的" 隔离策略:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny
|
||||
spec:
|
||||
podSelector:
|
||||
```
|
||||
|
||||
这可以确保即使Pod在未被其他任何网络策略所选择的情况下仍能被隔离。
|
||||
|
||||
或者,如果用户希望允许一个命名空间下的所有Pod的所有通信 (即使已经添加了策略,使得一些pod被 "隔离"),仍可以创建一个明确允许所有通信的策略:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-all
|
||||
spec:
|
||||
podSelector:
|
||||
ingress:
|
||||
- {}
|
||||
```
|
||||
|
||||
## 下一步呢?
|
||||
|
||||
- 查看 [声明网络策略](/docs/tasks/administer-cluster/declare-network-policy/)
|
||||
来进行更多的示例演练
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: my-nginx
|
||||
labels:
|
||||
run: my-nginx
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
selector:
|
||||
run: my-nginx
|
||||
@@ -0,0 +1,20 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,630 @@
|
||||
---
|
||||
approvers:
|
||||
- bprashanth
|
||||
title: Service
|
||||
redirect_from:
|
||||
- "/docs/user-guide/services/"
|
||||
- "/docs/user-guide/services/index.html"
|
||||
---
|
||||
|
||||
|
||||
|
||||
Kubernetes [`Pod`](/docs/user-guide/pods) 是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。
|
||||
通过 [`ReplicationController`](/docs/user-guide/replication-controller) 能够动态地创建和销毁 `Pod`(例如,需要进行扩缩容,或者执行 [滚动升级](/docs/user-guide/kubectl/v1.7/#rolling-update))。
|
||||
每个 `Pod` 都会获取它自己的 IP 地址,即使这些 IP 地址不总是稳定可依赖的。
|
||||
这会导致一个问题:在 Kubernetes 集群中,如果一组 `Pod`(称为 backend)为其它 `Pod` (称为 frontend)提供服务,那么那些 frontend 该如何发现,并连接到这组 `Pod` 中的哪些 backend 呢?
|
||||
|
||||
|
||||
|
||||
关于 `Service`
|
||||
|
||||
|
||||
|
||||
Kubernetes `Service` 定义了这样一种抽象:一个 `Pod` 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。
|
||||
这一组 `Pod` 能够被 `Service` 访问到,通常是通过 [`Label Selector`](/docs/concepts/overview/working-with-objects/labels/#label-selectors)(查看下面了解,为什么可能需要没有 selector 的 `Service`)实现的。
|
||||
|
||||
|
||||
|
||||
举个例子,考虑一个图片处理 backend,它运行了3个副本。这些副本是可互换的 —— frontend 不需要关心它们调用了哪个 backend 副本。
|
||||
然而组成这一组 backend 程序的 `Pod` 实际上可能会发生变化,frontend 客户端不应该也没必要知道,而且也不需要跟踪这一组 backend 的状态。
|
||||
`Service` 定义的抽象能够解耦这种关联。
|
||||
|
||||
|
||||
|
||||
对 Kubernetes 集群中的应用,Kubernetes 提供了简单的 `Endpoints` API,只要 `Service` 中的一组 `Pod` 发生变更,应用程序就会被更新。
|
||||
对非 Kubernetes 集群中的应用,Kubernetes 提供了基于 VIP 的网桥的方式访问 `Service`,再由 `Service` 重定向到 backend `Pod`。
|
||||
|
||||
{{< toc >}}
|
||||
|
||||
|
||||
|
||||
## 定义 Service
|
||||
|
||||
|
||||
|
||||
一个 `Service` 在 Kubernetes 中是一个 REST 对象,和 `Pod` 类似。
|
||||
像所有的 REST 对象一样, `Service` 定义可以基于 POST 方式,请求 apiserver 创建新的实例。
|
||||
例如,假定有一组 `Pod`,它们对外暴露了 9376 端口,同时还被打上 `"app=MyApp"` 标签。
|
||||
|
||||
```yaml
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
selector:
|
||||
app: MyApp
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 9376
|
||||
```
|
||||
|
||||
|
||||
|
||||
上述配置将创建一个名称为 “my-service” 的 `Service` 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 `"app=MyApp"` 的 `Pod` 上。
|
||||
这个 `Service` 将被指派一个 IP 地址(通常称为 “Cluster IP”),它会被服务的代理使用(见下面)。
|
||||
该 `Service` 的 selector 将会持续评估,处理结果将被 POST 到一个名称为 “my-service” 的 `Endpoints` 对象上。
|
||||
|
||||
|
||||
|
||||
需要注意的是, `Service` 能够将一个接收端口映射到任意的 `targetPort`。
|
||||
默认情况下,`targetPort` 将被设置为与 `port` 字段相同的值。
|
||||
可能更有趣的是,`targetPort` 可以是一个字符串,引用了 backend `Pod` 的一个端口的名称。
|
||||
但是,实际指派给该端口名称的端口号,在每个 backend `Pod` 中可能并不相同。
|
||||
对于部署和设计 `Service` ,这种方式会提供更大的灵活性。
|
||||
例如,可以在 backend 软件下一个版本中,修改 Pod 暴露的端口,并不会中断客户端的调用。
|
||||
|
||||
|
||||
|
||||
Kubernetes `Service` 能够支持 `TCP` 和 `UDP` 协议,默认 `TCP` 协议。
|
||||
|
||||
|
||||
|
||||
### 没有 selector 的 Service
|
||||
|
||||
|
||||
|
||||
Servcie 抽象了该如何访问 Kubernetes `Pod`,但也能够抽象其它类型的 backend,例如:
|
||||
|
||||
* 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
|
||||
* 希望服务指向另一个 [`Namespace`](/docs/user-guide/namespaces) 中或其它集群中的服务。
|
||||
* 正在将工作负载转移到 Kubernetes 集群,和运行在 Kubernetes 集群之外的 backend。
|
||||
|
||||
在任何这些场景中,都能够定义没有 selector 的 `Service` :
|
||||
|
||||
```yaml
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 9376
|
||||
```
|
||||
|
||||
|
||||
由于这个 `Service` 没有 selector,就不会创建相关的 `Endpoints` 对象。可以手动将 `Service` 映射到指定的 `Endpoints`:
|
||||
|
||||
```yaml
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-service
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 1.2.3.4
|
||||
ports:
|
||||
- port: 9376
|
||||
```
|
||||
|
||||
|
||||
|
||||
注意:Endpoint IP 地址不能是 loopback(127.0.0.0/8)、 link-local(169.254.0.0/16)、或者 link-local 多播(224.0.0.0/24)。
|
||||
|
||||
|
||||
|
||||
访问没有 selector 的 `Service`,与有 selector 的 `Service` 的原理相同。请求将被路由到用户定义的 Endpoint(该示例中为 `1.2.3.4:9376`)。
|
||||
|
||||
|
||||
|
||||
ExternalName `Service` 是 `Service` 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。
|
||||
相反地,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。
|
||||
|
||||
```yaml
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-service
|
||||
namespace: prod
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: my.database.example.com
|
||||
```
|
||||
|
||||
|
||||
|
||||
当查询主机 `my-service.prod.svc.CLUSTER`时,集群的 DNS 服务将返回一个值为 `my.database.example.com` 的 `CNAME` 记录。
|
||||
访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。
|
||||
如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 `Service` 的 `type`。
|
||||
|
||||
|
||||
|
||||
## VIP 和 Service 代理
|
||||
|
||||
|
||||
|
||||
在 Kubernetes 集群中,每个 Node 运行一个 `kube-proxy` 进程。`kube-proxy` 负责为 `Service` 实现了一种 VIP(虚拟 IP)的形式,而不是 `ExternalName` 的形式。
|
||||
在 Kubernetes v1.0 版本,代理完全在 userspace。在 Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。
|
||||
从 Kubernetes v1.2 起,默认就是 iptables 代理。
|
||||
|
||||
|
||||
|
||||
在 Kubernetes v1.0 版本,`Service` 是 “4层”(TCP/UDP over IP)概念。
|
||||
在 Kubernetes v1.1 版本,新增了 `Ingress` API(beta 版),用来表示 “7层”(HTTP)服务。
|
||||
|
||||
|
||||
|
||||
### userspace 代理模式
|
||||
|
||||
|
||||
|
||||
这种模式,kube-proxy 会监视 Kubernetes master 对 `Service` 对象和 `Endpoints` 对象的添加和移除。
|
||||
对每个 `Service`,它会在本地 Node 上打开一个端口(随机选择)。
|
||||
任何连接到“代理端口”的请求,都会被代理到 `Service` 的backend `Pods` 中的某个上面(如 `Endpoints` 所报告的一样)。
|
||||
使用哪个 backend `Pod`,是基于 `Service` 的 `SessionAffinity` 来确定的。
|
||||
最后,它安装 iptables 规则,捕获到达该 `Service` 的 `clusterIP`(是虚拟 IP)和 `Port` 的请求,并重定向到代理端口,代理端口再代理请求到 backend `Pod`。
|
||||
|
||||
|
||||
|
||||
网络返回的结果是,任何到达 `Service` 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、`Service`、或 `Pod` 的任何信息。
|
||||
|
||||
|
||||
|
||||
默认的策略是,通过 round-robin 算法来选择 backend `Pod`。
|
||||
实现基于客户端 IP 的会话亲和性,可以通过设置 `service.spec.sessionAffinity` 的值为 `"ClientIP"` (默认值为 `"None"`)。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### iptables 代理模式
|
||||
|
||||
|
||||
|
||||
这种模式,kube-proxy 会监视 Kubernetes master 对 `Service` 对象和 `Endpoints` 对象的添加和移除。
|
||||
对每个 `Service`,它会安装 iptables 规则,从而捕获到达该 `Service` 的 `clusterIP`(虚拟 IP)和端口的请求,进而将请求重定向到 `Service` 的一组 backend 中的某个上面。
|
||||
对于每个 `Endpoints` 对象,它也会安装 iptables 规则,这个规则会选择一个 backend `Pod`。
|
||||
|
||||
|
||||
|
||||
默认的策略是,随机选择一个 backend。
|
||||
实现基于客户端 IP 的会话亲和性,可以将 `service.spec.sessionAffinity` 的值设置为 `"ClientIP"` (默认值为 `"None"`)。
|
||||
|
||||
|
||||
|
||||
和 userspace 代理类似,网络返回的结果是,任何到达 `Service` 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、`Service`、或 `Pod` 的任何信息。
|
||||
这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 `Pod` 没有响应,iptables 代理能够自动地重试另一个 `Pod`,所以它需要依赖 [readiness probes](/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#defining-readiness-probes)。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 多端口 Service
|
||||
|
||||
|
||||
|
||||
很多 `Service` 需要暴露多个端口。对于这种情况,Kubernetes 支持在 `Service` 对象中定义多个端口。
|
||||
当使用多个端口时,必须给出所有的端口的名称,这样 Endpoint 就不会产生歧义,例如:
|
||||
|
||||
```yaml
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
selector:
|
||||
app: MyApp
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 9376
|
||||
- name: https
|
||||
protocol: TCP
|
||||
port: 443
|
||||
targetPort: 9377
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 选择自己的 IP 地址
|
||||
|
||||
|
||||
|
||||
在 `Service` 创建的请求中,可以通过设置 `spec.clusterIP` 字段来指定自己的集群 IP 地址。
|
||||
比如,希望替换一个已经已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。
|
||||
用户选择的 IP 地址必须合法,并且这个 IP 地址在 `service-cluster-ip-range` CIDR 范围内,这对 API Server 来说是通过一个标识来指定的。
|
||||
如果 IP 地址不合法,API Server 会返回 HTTP 状态码 422,表示值不合法。
|
||||
|
||||
|
||||
|
||||
### 为何不使用 round-robin DNS?
|
||||
|
||||
|
||||
|
||||
一个不时出现的问题是,为什么我们都使用 VIP 的方式,而不使用标准的 round-robin DNS,有如下几个原因:
|
||||
|
||||
* 长久以来,DNS 库都没能认真对待 DNS TTL、缓存域名查询结果
|
||||
* 很多应用只查询一次 DNS 并缓存了结果
|
||||
* 就算应用和库能够正确查询解析,每个客户端反复重解析造成的负载也是非常难以管理的
|
||||
|
||||
我们尽力阻止用户做那些对他们没有好处的事情,如果很多人都来问这个问题,我们可能会选择实现它。
|
||||
|
||||
|
||||
|
||||
## 服务发现
|
||||
|
||||
|
||||
|
||||
Kubernetes 支持2种基本的服务发现模式 —— 环境变量和 DNS。
|
||||
|
||||
|
||||
|
||||
### 环境变量
|
||||
|
||||
|
||||
|
||||
当 `Pod` 运行在 `Node` 上,kubelet 会为每个活跃的 `Service` 添加一组环境变量。
|
||||
它同时支持 [Docker links兼容](https://docs.docker.com/userguide/dockerlinks/) 变量(查看 [makeLinkVariables](http://releases.k8s.io/{{< param "githubbranch" >}}/pkg/kubelet/envvars/envvars.go#L49))、简单的 `{SVCNAME}_SERVICE_HOST` 和 `{SVCNAME}_SERVICE_PORT` 变量,这里 `Service` 的名称需大写,横线被转换成下划线。
|
||||
|
||||
|
||||
|
||||
举个例子,一个名称为 `"redis-master"` 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:
|
||||
|
||||
```shell
|
||||
REDIS_MASTER_SERVICE_HOST=10.0.0.11
|
||||
REDIS_MASTER_SERVICE_PORT=6379
|
||||
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
|
||||
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
|
||||
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
|
||||
REDIS_MASTER_PORT_6379_TCP_PORT=6379
|
||||
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
|
||||
```
|
||||
|
||||
|
||||
*这意味着需要有顺序的要求* —— `Pod` 想要访问的任何 `Service` 必须在 `Pod` 自己之前被创建,否则这些环境变量就不会被赋值。DNS 并没有这个限制。
|
||||
|
||||
### DNS
|
||||
|
||||
|
||||
|
||||
一个可选(尽管强烈推荐)[集群插件](http://releases.k8s.io/{{< param "githubbranch" >}}/cluster/addons/README.md) 是 DNS 服务器。
|
||||
DNS 服务器监视着创建新 `Service` 的 Kubernetes API,从而为每一个 `Service` 创建一组 DNS 记录。
|
||||
如果整个集群的 DNS 一直被启用,那么所有的 `Pod` 应该能够自动对 `Service` 进行名称解析。
|
||||
|
||||
|
||||
|
||||
例如,有一个名称为 `"my-service"` 的 `Service`,它在 Kubernetes 集群中名为 `"my-ns"` 的 `Namespace` 中,为 `"my-service.my-ns"` 创建了一条 DNS 记录。
|
||||
在名称为 `"my-ns"` 的 `Namespace` 中的 `Pod` 应该能够简单地通过名称查询找到 `"my-service"`。
|
||||
在另一个 `Namespace` 中的 `Pod` 必须限定名称为 `"my-service.my-ns"`。
|
||||
这些名称查询的结果是 Cluster IP。
|
||||
|
||||
|
||||
|
||||
Kubernetes 也支持对端口名称的 DNS SRV(Service)记录。
|
||||
如果名称为 `"my-service.my-ns"` 的 `Service` 有一个名为 `"http"` 的 `TCP` 端口,可以对 `"_http._tcp.my-service.my-ns"` 执行 DNS SRV 查询,得到 `"http"` 的端口号。
|
||||
|
||||
|
||||
|
||||
Kubernetes DNS 服务器是唯一的一种能够访问 `ExternalName` 类型的 Service 的方式。
|
||||
更多信息可以查看[DNS Pod 和 Service](/docs/concepts/services-networking/dns-pod-service/)。
|
||||
|
||||
|
||||
|
||||
## Headless Service
|
||||
|
||||
|
||||
|
||||
有时不需要或不想要负载均衡,以及单独的 Service IP。
|
||||
遇到这种情况,可以通过指定 Cluster IP(`spec.clusterIP`)的值为 `"None"` 来创建 `Headless` Service。
|
||||
|
||||
|
||||
|
||||
这个选项允许开发人员自由寻找他们自己的方式,从而降低与 Kubernetes 系统的耦合性。
|
||||
应用仍然可以使用一种自注册的模式和适配器,对其它需要发现机制的系统能够很容易地基于这个 API 来构建。
|
||||
|
||||
|
||||
|
||||
对这类 `Service` 并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。
|
||||
DNS 如何实现自动配置,依赖于 `Service` 是否定义了 selector。
|
||||
|
||||
|
||||
|
||||
### 配置 Selector
|
||||
|
||||
|
||||
|
||||
对定义了 selector 的 Headless Service,Endpoint 控制器在 API 中创建了 `Endpoints` 记录,并且修改 DNS 配置返回 A 记录(地址),通过这个地址直接到达 `Service` 的后端 `Pod` 上。
|
||||
|
||||
|
||||
|
||||
### 不配置 Selector
|
||||
|
||||
|
||||
|
||||
对没有定义 selector 的 Headless Service,Endpoint 控制器不会创建 `Endpoints` 记录。
|
||||
然而 DNS 系统会查找和配置,无论是:
|
||||
|
||||
* `ExternalName` 类型 Service 的 CNAME 记录
|
||||
* 记录:与 Service 共享一个名称的任何 `Endpoints`,以及所有其它类型
|
||||
|
||||
|
||||
|
||||
## 发布服务 —— 服务类型
|
||||
|
||||
对一些应用(如 Frontend)的某些部分,可能希望通过外部(Kubernetes 集群外部)IP 地址暴露 Service。
|
||||
|
||||
|
||||
|
||||
Kubernetes `ServiceTypes` 允许指定一个需要的类型的 Service,默认是 `ClusterIP` 类型。
|
||||
|
||||
`Type` 的取值以及行为如下:
|
||||
|
||||
* `ClusterIP`:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 `ServiceType`。
|
||||
* `NodePort`:通过每个 Node 上的 IP 和静态端口(`NodePort`)暴露服务。`NodePort` 服务会路由到 `ClusterIP` 服务,这个 `ClusterIP` 服务会自动创建。通过请求 `<NodeIP>:<NodePort>`,可以从集群的外部访问一个 `NodePort` 服务。
|
||||
* `LoadBalancer`:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 `NodePort` 服务和 `ClusterIP` 服务。
|
||||
* `ExternalName`:通过返回 `CNAME` 和它的值,可以将服务映射到 `externalName` 字段的内容(例如, `foo.bar.example.com`)。
|
||||
没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 `kube-dns` 才支持。
|
||||
|
||||
|
||||
|
||||
### NodePort 类型
|
||||
|
||||
如果设置 `type` 的值为 `"NodePort"`,Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 `Service`。该端口将通过 `Service` 的 `spec.ports[*].nodePort` 字段被指定。
|
||||
|
||||
|
||||
|
||||
|
||||
如果需要指定的端口号,可以配置 `nodePort` 的值,系统将分配这个端口,否则调用 API 将会失败(比如,需要关心端口冲突的可能性)。
|
||||
|
||||
|
||||
|
||||
这可以让开发人员自由地安装他们自己的负载均衡器,并配置 Kubernetes 不能完全支持的环境参数,或者直接暴露一个或多个 Node 的 IP 地址。
|
||||
|
||||
|
||||
|
||||
需要注意的是,Service 将能够通过 `<NodeIP>:spec.ports[*].nodePort` 和 `spec.clusterIp:spec.ports[*].port` 而对外可见。
|
||||
|
||||
|
||||
|
||||
### LoadBalancer 类型
|
||||
|
||||
|
||||
|
||||
使用支持外部负载均衡器的云提供商的服务,设置 `type` 的值为 `"LoadBalancer"`,将为 `Service` 提供负载均衡器。
|
||||
负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 `Service` 的 `status.loadBalancer` 字段被发布出去。
|
||||
|
||||
```yaml
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
selector:
|
||||
app: MyApp
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 9376
|
||||
nodePort: 30061
|
||||
clusterIP: 10.0.171.239
|
||||
loadBalancerIP: 78.11.24.19
|
||||
type: LoadBalancer
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
- ip: 146.148.47.155
|
||||
```
|
||||
|
||||
|
||||
来自外部负载均衡器的流量将直接打到 backend `Pod` 上,不过实际它们是如何工作的,这要依赖于云提供商。
|
||||
在这些情况下,将根据用户设置的 `loadBalancerIP` 来创建负载均衡器。
|
||||
某些云提供商允许设置 `loadBalancerIP`。如果没有设置 `loadBalancerIP`,将会给负载均衡器指派一个临时 IP。
|
||||
如果设置了 `loadBalancerIP`,但云提供商并不支持这种特性,那么设置的 `loadBalancerIP` 值将会被忽略掉。
|
||||
|
||||
|
||||
|
||||
### AWS 内部负载均衡器
|
||||
在混合云环境中,有时从虚拟私有云(VPC)环境中的服务路由流量是非常有必要的。
|
||||
可以通过在 `Service` 中增加 `annotation` 来实现,如下所示:
|
||||
|
||||
```yaml
|
||||
[...]
|
||||
metadata:
|
||||
name: my-service
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
|
||||
[...]
|
||||
```
|
||||
|
||||
|
||||
在水平分割的 DNS 环境中,需要两个 `Service` 来将外部和内部的流量路由到 Endpoint 上。
|
||||
|
||||
|
||||
|
||||
### AWS SSL 支持
|
||||
对运行在 AWS 上部分支持 SSL 的集群,从 1.3 版本开始,可以为 `LoadBalancer` 类型的 `Service` 增加两个 annotation:
|
||||
|
||||
```
|
||||
metadata:
|
||||
name: my-service
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012
|
||||
```
|
||||
|
||||
|
||||
|
||||
第一个 annotation 指定了使用的证书。它可以是第三方发行商发行的证书,这个证书或者被上传到 IAM,或者由 AWS 的证书管理器创建。
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
name: my-service
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: (https|http|ssl|tcp)
|
||||
```
|
||||
|
||||
|
||||
第二个 annotation 指定了 `Pod` 使用的协议。
|
||||
对于 HTTPS 和 SSL,ELB 将期望该 `Pod` 基于加密的连接来认证自身。
|
||||
|
||||
|
||||
|
||||
HTTP 和 HTTPS 将选择7层代理:ELB 将中断与用户的连接,当转发请求时,会解析 Header 信息并添加上用户的 IP 地址(`Pod` 将只能在连接的另一端看到该 IP 地址)。
|
||||
|
||||
TCP 和 SSL 将选择4层代理:ELB 将转发流量,并不修改 Header 信息。
|
||||
|
||||
|
||||
|
||||
### 外部 IP
|
||||
|
||||
如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes `Service` 会被暴露给这些 `externalIPs`。
|
||||
通过外部 IP(作为目的 IP 地址)进入到集群,打到 `Service` 的端口上的流量,将会被路由到 `Service` 的 Endpoint 上。
|
||||
`externalIPs` 不会被 Kubernetes 管理,它属于集群管理员的职责范畴。
|
||||
|
||||
根据 `Service` 的规定,`externalIPs` 可以同任意的 `ServiceType` 来一起指定。
|
||||
在上面的例子中,`my-service` 可以在 80.11.12.10:80(外部 IP:端口)上被客户端访问。
|
||||
|
||||
```yaml
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
selector:
|
||||
app: MyApp
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 9376
|
||||
externalIPs:
|
||||
- 80.11.12.10
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 不足之处
|
||||
|
||||
为 VIP 使用 userspace 代理,将只适合小型到中型规模的集群,不能够扩展到上千 `Service` 的大型集群。
|
||||
查看 [最初设计方案](http://issue.k8s.io/1107) 获取更多细节。
|
||||
|
||||
|
||||
|
||||
使用 userspace 代理,隐藏了访问 `Service` 的数据包的源 IP 地址。
|
||||
这使得一些类型的防火墙无法起作用。
|
||||
iptables 代理不会隐藏 Kubernetes 集群内部的 IP 地址,但却要求客户端请求必须通过一个负载均衡器或 Node 端口。
|
||||
|
||||
|
||||
|
||||
`Type` 字段支持嵌套功能 —— 每一层需要添加到上一层里面。
|
||||
不会严格要求所有云提供商(例如,GCE 就没必要为了使一个 `LoadBalancer` 能工作而分配一个 `NodePort`,但是 AWS 需要 ),但当前 API 是强制要求的。
|
||||
|
||||
|
||||
|
||||
## 未来工作
|
||||
|
||||
未来我们能预见到,代理策略可能会变得比简单的 round-robin 均衡策略有更多细微的差别,比如 master 选举或分片。
|
||||
我们也能想到,某些 `Service` 将具有 “真正” 的负载均衡器,这种情况下 VIP 将简化数据包的传输。
|
||||
|
||||
|
||||
|
||||
我们打算为 L7(HTTP)`Service` 改进我们对它的支持。
|
||||
|
||||
我们打算为 `Service` 实现更加灵活的请求进入模式,这些 `Service` 包含当前 `ClusterIP`、`NodePort` 和 `LoadBalancer` 模式,或者更多。
|
||||
|
||||
|
||||
|
||||
## VIP 的那些骇人听闻的细节
|
||||
|
||||
对很多想使用 `Service` 的人来说,前面的信息应该足够了。
|
||||
然而,有很多内部原理性的内容,还是值去理解的。
|
||||
|
||||
|
||||
|
||||
### 避免冲突
|
||||
|
||||
Kubernetes 最主要的哲学之一,是用户不应该暴露那些能够导致他们操作失败、但又不是他们的过错的场景。
|
||||
这种场景下,让我们来看一下网络端口 —— 用户不应该必须选择一个端口号,而且该端口还有可能与其他用户的冲突。
|
||||
这就是说,在彼此隔离状态下仍然会出现失败。
|
||||
|
||||
|
||||
|
||||
为了使用户能够为他们的 `Service` 选择一个端口号,我们必须确保不能有2个 `Service` 发生冲突。
|
||||
我们可以通过为每个 `Service` 分配它们自己的 IP 地址来实现。
|
||||
|
||||
|
||||
|
||||
为了保证每个 `Service` 被分配到一个唯一的 IP,需要一个内部的分配器能够原子地更新 etcd 中的一个全局分配映射表,这个更新操作要先于创建每一个 `Service`。
|
||||
为了使 `Service` 能够获取到 IP,这个映射表对象必须在注册中心存在,否则创建 `Service` 将会失败,指示一个 IP 不能被分配。
|
||||
一个后台 Controller 的职责是创建映射表(从 Kubernetes 的旧版本迁移过来,旧版本中是通过在内存中加锁的方式实现),并检查由于管理员干预和清除任意 IP 造成的不合理分配,这些 IP 被分配了但当前没有 `Service` 使用它们。
|
||||
|
||||
|
||||
|
||||
### IP 和 VIP
|
||||
|
||||
不像 `Pod` 的 IP 地址,它实际路由到一个固定的目的地,`Service` 的 IP 实际上不能通过单个主机来进行应答。
|
||||
相反,我们使用 `iptables`(Linux 中的数据包处理逻辑)来定义一个虚拟IP地址(VIP),它可以根据需要透明地进行重定向。
|
||||
当客户端连接到 VIP 时,它们的流量会自动地传输到一个合适的 Endpoint。
|
||||
环境变量和 DNS,实际上会根据 `Service` 的 VIP 和端口来进行填充。
|
||||
|
||||
|
||||
|
||||
#### Userspace
|
||||
|
||||
作为一个例子,考虑前面提到的图片处理应用程序。
|
||||
当创建 backend `Service` 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。
|
||||
假设 `Service` 的端口是 1234,该 `Service` 会被集群中所有的 `kube-proxy` 实例观察到。
|
||||
当代理看到一个新的 `Service`, 它会打开一个新的端口,建立一个从该 VIP 重定向到新端口的 iptables,并开始接收请求连接。
|
||||
|
||||
|
||||
|
||||
当一个客户端连接到一个 VIP,iptables 规则开始起作用,它会重定向该数据包到 `Service代理` 的端口。
|
||||
`Service代理` 选择一个 backend,并将客户端的流量代理到 backend 上。
|
||||
|
||||
这意味着 `Service` 的所有者能够选择任何他们想使用的端口,而不存在冲突的风险。
|
||||
客户端可以简单地连接到一个 IP 和端口,而不需要知道实际访问了哪些 `Pod`。
|
||||
|
||||
|
||||
|
||||
#### Iptables
|
||||
|
||||
再次考虑前面提到的图片处理应用程序。
|
||||
当创建 backend `Service` 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。
|
||||
假设 `Service` 的端口是 1234,该 `Service` 会被集群中所有的 `kube-proxy` 实例观察到。
|
||||
当代理看到一个新的 `Service`, 它会安装一系列的 iptables 规则,从 VIP 重定向到 per-`Service` 规则。
|
||||
该 per-`Service` 规则连接到 per-`Endpoint` 规则,该 per-`Endpoint` 规则会重定向(目标 NAT)到 backend。
|
||||
|
||||
|
||||
|
||||
当一个客户端连接到一个 VIP,iptables 规则开始起作用。一个 backend 会被选择(或者根据会话亲和性,或者随机),数据包被重定向到这个 backend。
|
||||
不像 userspace 代理,数据包从来不拷贝到用户空间,kube-proxy 不是必须为该 VIP 工作而运行,并且客户端 IP 是不可更改的。
|
||||
当流量打到 Node 的端口上,或通过负载均衡器,会执行相同的基本流程,但是在那些案例中客户端 IP 是可以更改的。
|
||||
|
||||
|
||||
|
||||
## API 对象
|
||||
|
||||
在 Kubernetes REST API 中,Service 是 top-level 资源。关于 API 对象的更多细节可以查看:[Service API 对象](/docs/api-reference/{{< param "version" >}}/#service-v1-core)。
|
||||
|
||||
|
||||
|
||||
## 更多信息
|
||||
|
||||
阅读 [使用 Service 连接 Frontend 到 Backend](/docs/tutorials/connecting-apps/connecting-frontend-backend/)。
|
||||
Reference in New Issue
Block a user