4-k8s笔记-深入掌握Service

第4章 深入掌握Service
4.1 Service定义详解
4.2 Service的基本用法
4.2.1 多端口Service
4.2.2 外部服务Service
4.3 Headless Service
4.3.1 自定义SeedProvider
4.3.2 通过Service动态查找Pod
4.3.3 Cassandra集群中新节点的自动添加
4.4 从集群外部访问Pod或Service
4.4.1 将容器应用的端口号映射到物理机
4.4.2 将Service的端口号映射到物理机
4.5 DNS服务搭建和配置指南
4.5.1 在创建DNS服务之前修改每个Node上kubelet的启动参数
4.5.2 创建CoreDNS应用
4.5.3 服务名的DNS解析
4.5.4 CoreDNS的配置说明
4.5.5 Pod级别的DNS配置说明
4.6 Ingress:HTTP 7层路由机制
4.6.1 创建Ingress Controller和默认的backend服务
4.6.2 定义Ingress策略
4.6.3 客户端访问http://mywebsite.com/demo
4.6.4 Ingress的策略配置技巧
4.6.5 Ingress的TLS安全设置

Service是Kubernetes的核心概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。
本章对Service的使用进行详细说明,包括Service的负载均衡机制、如何访问Service、Headless Service、DNS服务的机制和实践、Ingress 7层路由机制等。

4.1 Service定义详解
YAML格式的Service定义文件的完整内容如下:
apiVersion: v1 // Required v1
kind: Service // Required Service
metadata: // Required 元数据
name: string // Required Service名称
namespace: string // Required 命名空间
labels: list-自定义标签属性列表
- name: string
annotations: list-自定义注解属性列表
- name: string
spec: // Required object-详细描述
selector: [] // Required list- Labels Selector配置,将选择具有指定Label标签的Pod作为管理范围
type: string // Required Service的类型,指定Service的访问方式,取值包括:ClusterIP、NodePort、LoadBalancer。默认值为ClusterIP。
ClusterIP:虚拟的服务IP地址,用于kubernetes集群内部的pod访问,在Node上kube-proxy通过设置的iptables规则进行转发。
NodePort:使用宿主机的端口,使能够访问各Node的外部客户端通过Node的IP地址和端口号就能访问服务。
LoadBalancer:使用外接负载器完成到服务负载分发,需要在spec.status.loadBalancer字段指定外部负载均衡器的IP地址,并同时定义nodePort和clusterIP,用于共有云环境。
clusterIP: string 虚拟服务IP地址。当type=ClusterIP时,如果不指定,则系统进行自动分配,也可以手动指定。当type=LoadBalancer时,则需要指定。
sessionAffinity: string 是否支持session,可选值为ClientIP,默认值为空。ClientIP:表示将同一客户端的访问请求都转发到同一个后端Pod。
ports: list-Service需要暴露的端口列表
- name: string 端口名称
protocol: string 端口协议,支持TCP和UDP,默认值为TCP
port: int 服务监听的端口号
targetPort: int 需要转发到后端Pod的端口号
nodePort: int 当spec.type=NodePort时,指定映射到物理机的端口号
status: 当spec.type=LoadBalancer时,设置外部负载均衡器的地址,用于公有云环境
loadBalancer: 外部负载均衡器
ingress: 外部负载均衡器
ip: string 外部负载均衡器的IP地址
hostname: string 外部负载均衡器的主机名

4.2 Service的基本用法
一般来说,对外提供服务的应用程序需要通过某种机制来实现,对于容器应用最简便的方式就是通过TCP/IP机制及监听IP和端口号来实现。
例如,定义一个提供Web服务的RC,由两个Tomcat容器副本组成,每个容器都通过containerPort设置提供服务的端口号为8080。
RC的定义文件webapp-rc.yaml如下:
apiVersion: v1
kind: ReplicationController
metadata:
name: webapp
spec:
replicas: 2
template:
metadata:
name: webapp
labels:
app: webapp
spec:
- name: webapp
image: tomcat
ports:
- containerPort: 8080
创建该RC:
# kubectl create -f webapp-rc.yaml
获取Pod的IP地址:
# kubectl get pods -l app=webapp -o yaml | grep podIP
可以直接通过这两个Pod的IP地址和端口号访问Tomcat服务:
# curl podIP:8080
直接通过Pod的IP地址和端口号可以访问到容器应用内的服务,但是Pod的IP地址是不可靠的,
例如当Pod所在的Node发生故障时,Pod将被Kubernetes重新调度到另一个Node,Pod的IP地址将发生变化。
更重要的是,如果容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的分发。
Kubernetes中的Service就是用于解决这些问题的核心组件。
以前面创建的webapp应用为例,为了让客户端应用访问到两个Tomcat Pod实例,需要创建一个Service来提供服务。
Kubernetes提供了一种快速的方法,即通过kubectl expose命令来创建Service:
# kubectl expose rc webapp
查看新创建的Service,可以看到系统为它分配了一个虚拟的IP地址(ClusterIP),Service所需的端口号则从Pod中的containerPort复制而来:
# kubectl get svc
接下来就可以通过Service的IP地址和Service的端口号访问该Service了:
# curl ServiceIP:ServicePort
这里,对Service地址169.169.235.79:8080的访问被自动负载分发到了后端两个Pod之一:172.17.1.3:8080或172.17.1.4:8080。
除了使用kubectl expose命令创建Service,我们也可以通过配置文件定义Service,再通过kubectl create命令进行创建。
例如对于前面的webapp应用,我们可以设置一个Service(webapp-svc.yaml),代码如下:
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
selector: webapp
ports:
- port: 8081
targetPort: 8080
Service定义中的关键字段是ports和selector。
本例中ports定义部分指定了Service所需的虚拟端口号为8081,由于与Pod容器端口号8080不一样,所以需要再通过targetPort来指定后端Pod的端口号。
selector定义部分设置的是后端Pod所拥有的label:app=webapp。
创建该Service并查看其ClusterIP地址:
# kubectl create -f webapp-svc.yaml
# kubectl get svc
通过Service的IP地址和Service的端口号进行访问:
# curl 169.169.28.190:8081
同样,对Service地址169.169.28.190:8081的访问被自动负载分发到了后端两个Pod之一:172.17.1.3:8080或172.17.1.4:8080。
目前Kubernetes提供了两种负载分发策略:RoundRobin和SessionAffinity,具体说明如下:
RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上。
SessionAffinity:基于客户端IP地址进行会话保持的模式,
即第1次将某个客户端发起的请求转发到后端的某个Pod上,之后从相同的客户端发起的请求都将被转发到后端相同的Pod上。
在默认情况下,Kubernetes采用RoundRobin模式对客户端请求进行负载分发,
但我们也可以通过设置service.spec.sessionAffinity=ClientIP来启用SessionAffinity策略。
这样,同一个客户端IP发来的请求就会被转发到后端固定的某个Pod上了。
通过Service的定义,Kubernetes实现了一种分布式应用统一入口的定义和负载均衡机制。
Service还可以进行其他类型的设置,例如设置多个端口号、直接设置为集群外部服务,或实现为Headless Service(无头服务)模式(将在4.3节介绍)。

4.2.1 多端口Service
有时一个容器应用也可能提供多个端口的服务,那么在Service的定义中也可以相应地设置为将多个端口对应到多个应用服务。
在下面的例子中,Service设置了两个端口号,并且为每个端口号都进行了命名:
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
selector: webapp
ports:
- port: 8081
targetPort: 8080
name: web
- port: 8005
targetPort: 8005
name: management
另一个例子是两个端口号使用了不同的4层协议—TCP和UDP:
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
selector: webapp
ports:
- name: web
protocol: TCP
port: 8081
targetPort: 8080
nodePort: 8081
- name: management
protocol: UDP
port: 8005
targetPort: 8005
nodePort: 8005

4.2.2 外部服务Service
在某些环境中,应用系统需要将一个外部数据库作为后端服务进行连接,或将另一个集群或Namespace中的服务作为服务的后端,
这时可以通过创建一个无Label Selector的Service来实现:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 80
通过该定义创建的是一个不带标签选择器的Service,即无法选择后端的Pod,系统不会自动创建Endpoint,
因此需要手动创建一个和该Service同名的Endpoint,用于指向实际的后端访问地址。
创建Endpoint的配置文件内容如下:
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- IP: 1.2.3.4
ports:
- port: 80
如图4.1所示,访问没有标签选择器的Service和带有标签选择器的Service一样,请求将会被路由到由用户手动定义的后端Endpoint上。

4.3 Headless Service
在某些应用场景中,开发人员希望自己控制负载均衡的策略,不使用Service提供的默认负载均衡的功能,或者应用程序希望知道属于同组服务的其他实例。
Kubernetes提供了Headless Service来实现这种功能,即不为Service设置ClusterIP(入口IP地址),仅通过Label Selector将后端的Pod列表返回给调用的客户端。
例如:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
clusterIP: None
selector:
app: nginx
这样,Service就不再具有一个特定的ClusterIP地址,对其进行访问将获得包含Label“app=nginx”的全部Pod列表,然后客户端程序自行决定如何处理这个Pod列表。
例如,StatefulSet就是使用Headless Service为客户端返回多个服务地址的。
对于“去中心化”类的应用集群,Headless Service将非常有用。
下面以搭建Cassandra集群为例,看看如何通过对Headless Service的巧妙使用,自动实现应用集群的创建。
Apache Cassandra是一套开源分布式NoSQL数据库系统,主要特点为它不是单个数据库,而是由一组数据库节点共同构成的一个分布式的集群数据库。
由于Cassandra使用的是“去中心化”模式,所以在集群里的一个节点启动之后,需要一个途径获知集群中新节点的加入。
Cassandra使用了Seed(种子)来完成集群中节点之间的相互查找和通信。
通过对Headless Service的使用,实现了Cassandra各节点之间的相互查找和集群的自动搭建。
主要步骤包括:自定义SeedProvider;通过Headless Service自动查找后端Pod;自动添加新Cassandra节点。

4.3.1 自定义SeedProvider
在本例中使用了一个自定义的SeedProvider类来完成新节点的查询和添加,类名为io.k8s.cassandra.KubernetesSeedProvider。
KubernetesSeedProvider.java类的源代码可以从
https://github.com/kubernetes/kubernetes/blob/master/examples/storage/cassandra/java/src/main/java/io/k8s/cassandra/KubernetesSeedProvider.java获取。
定制的KubernetesSeedProvider类将使用REST API来访问Kubernetes Master,
然后通过查询name=cassandra的服务(Headless Service将返回Pod列表)完成对其他“节点”的查找。

4.3.2 通过Service动态查找Pod
在KubernetesSeedProvider类中,通过查询环境变量CASSANDRA_SERVICE的值来获得服务的名称。这样就要求Service在Pod之前创建。
如果我们已经创建好DNS服务(详见4.5节的说明),那么也可以直接使用服务的名称而无须使用环境变量。
回顾一下Service的概念。Service通常用作一个负载均衡器,供Kubernetes集群中其他应用(Pod)对属于该Service的一组Pod进行访问。
由于Pod的创建和销毁都会实时更新Service的Endpoint数据,所以可以动态地对Service的后端Pod进行查询。
Cassandra的“去中心化”设计使得Cassandra集群中的一个Cassandra实例(节点)只需要查询到其他节点,即可自动组成一个集群,
正好可以使用Service的这个特性查询到新增的节点。
图4.2描述了Cassandra新节点加入集群的过程。
在Kubernetes系统中,首先需要为Cassandra集群定义一个Service(cassandra-service.yaml):
apiVersion: v1
kind: Service
metadata:
name: cassandra
labels:
name: cassandra
spec:
ports:
- port: 9042
selector:
name: cassandra
在Service的定义中指定Label Selector为name=cassandra。
使用kubectl create命令创建这个Service:
# kubectl create -f cassandra-service.yaml
然后,创建一个Cassandra Pod(cassandra-rc.yaml):
apiVersion: v1
kind: ReplicationController
metadata:
name: cassandra
labels:
name: cassandra
spec:
replicas: 1
selector:
name: cassandra
template:
metadata:
labels:
name: cassandra
spec:
containers:
- command:
- /run.sh
resources:
limits:
cpu: 0.5
env:
- name: MAX_HEAP_SIZE
value: 512M
- name: HEAP_NEWSIZE
value: 100M
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: gcr.io/google_containers/cassandra:v5
name: cassandra
ports:
- containerPort: 9042
name: cql
- containerPort: 9160
name: thrift
volumeMounts:
- mountPath: /cassandra_data
name: data
volume:
- name: data
emptyDir: {}
# kubectl create -f cassandra-rc.yaml
现在,一个Cassandra Pod运行起来了,但还没有组成Cassandra集群。

4.3.3 Cassandra集群中新节点的自动添加
现在,我们使用Kubernetes提供的Scale(扩容和缩容)机制对Cassandra集群进行扩容:
# kubectl scale rc cassandra --replicas=2
查看Pod,可以看到RC创建并启动了一个新的Pod:
# kubectl get pods -l="name=cassandra"
使用Cassandra提供的nodetool工具对任一cassandra实例(Pod)进行访问来验证Cassandra集群的状态。
下面的命令将访问名为cassandra的Pod(访问cassandra-g52t3也能获得相同的结果):
# kubectl exec -it cassandra -- nodetool status
可以看到在Cassandra集群中有两个节点处于正常运行状态(Up and Normal,UN)。
该结果中的两个IP地址为两个Cassandra Pod的IP地址。
内部的过程为:每个Cassandra节点(Pod)都通过API访问Kubernetes Master,查询名为cassandra的Service的Endpoint(即Cassandra节点),
若发现有新节点加入,就进行添加操作,最后成功组成一个Cassandra集群。
我们再增加两个Cassandra实例:
# kubectl scale rc cassandra --replicas=4
用nodetool工具查看Cassandra集群的状态:
# kubectl exec -it cassandra -- nodetool status
可以看到4个Cassandra节点都加入Cassandra集群中了。
另外,可以通过查看Cassandra Pod的日志来看到新节点加入集群的记录:
# kubectl logs cassandra-xxxx
本例描述了一种通过API查询Service来完成动态Pod发现的应用场景。
对于类似于Cassandra的去中心化集群类应用,都可以使用Headless Service查询后端Endpoint这种巧妙的方法来实现对应用集群(属于同一Service)中新加入节点的查找。

4.4 从集群外部访问Pod或Service
由于Pod和Service都是Kubernetes集群范围内的虚拟概念,所以集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问它们。
为了让外部客户端可以访问这些服务,可以将Pod或Service的端口号映射到宿主机,以使客户端应用能够通过物理机访问容器应用。

4.4.1 将容器应用的端口号映射到物理机
(1)通过设置容器级别的hostPort(pod-hostport.yaml),将容器应用的端口号映射到物理机上:
apiVersion: v1
king: Pod
metadata:
name: webapp
labels:
name: webapp
spec:
containers:
- name: webapp
image: tomcat
ports:
- containerPort: 8080
hostPort: 8081
通过kubectl create命令创建这个Pod:
# kubectl create -f pod-hostport.yaml
通过物理机的IP地址和8081端口号访问Pod内的容器服务:
# curl 物理机IP:8081

(2)通过设置Pod级别的hostNetwork=true(pod-hostnetwork.yaml),该Pod中所有容器的端口号都将被直接映射到物理机上。
在设置hostNetwork=true时需要注意,
在容器的ports定义部分如果不指定hostPort,则默认hostPort等于containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值:
apiVersion: v1
king: Pod
metadata:
name: webapp
labels:
name: webapp
spec:
hostNetwork: true
containers:
- name: webapp
image: tomcat
imagePullPolicy: Never
ports:
- containerPort: 8080
创建这个Pod:
# kubectl create -f pod-hostnetwork.yaml
通过物理机的IP地址和8080端口号访问Pod内的容器服务:
# curl 物理机IP:8080

4.4.2 将Service的端口号映射到物理机
(1)通过设置nodePort(service-nodeport.yaml)映射到物理机,同时设置Service的类型为NodePort:
apiVersion: v1
king: Service
metadata:
name: webapp
spec:
type: NodePort
selector:
app: webapp
ports:
- port: 8080
targetPort: 8080
nodePort: 8081
创建这个Service:
# kubectl create -f service-nodeport.yaml
系统提示信息说明:由于要使用物理机的端口号,所以需要在防火墙上做好相应的配置,以使外部客户端能够访问到该端口。
通过物理机的IP地址和nodePort 8081端口号访问服务:
# curl 物理机IP:nodePort 8081端口号
同样,对该Service的访问也将被负载分发到后端的多个Pod上。

(2)通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址(service-loadbalancer)。
这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。
在下面的例子中,status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。
对该Service的访问请求将会通过LoadBalancer转发到后端Pod上,负载分发的实现方式则依赖于云服务商提供的LoadBalancer的实现机制:
apiVersion: v1
king: Service
metadata:
name: my-service
spec:
selector:
app: webapp
ports:
- port: 80
protocol: TCP
targetPort: 9376
nodePort: 30061
clusterIP: 10.0.171.239
loadBalancerIP: 78.11.24.19
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 146.148.47.155

4.5 DNS服务搭建和配置指南
作为服务发现机制的基本功能,在集群内需要能够通过服务名对服务进行访问,这就需要一个集群范围内的DNS服务来完成从服务名到ClusterIP的解析。
DNS服务在Kubernetes的发展过程中经历了3个阶段,接下来会进行讲解。
在Kubernetes 1.2版本时,DNS服务是由SkyDNS提供的,它由4个容器组成:kube2sky、skydns、etcd和healthz。
kube2sky容器监控Kubernetes中Service资源的变化,根据Service的名称和IP地址信息生成DNS记录,并将其保存到etcd中;
skydns容器从etcd中读取DNS记录,并为客户端容器应用提供DNS查询服务;
healthz容器提供对skydns服务的健康检查功能。
图4.3展现了SkyDNS的总体架构。
从Kubernetes 1.4版本开始,SkyDNS组件便被KubeDNS替换,主要考虑是SkyDNS组件之间通信较多,整体性能不高。
KubeDNS由3个容器组成:kubedns、dnsmasq和sidecar,去掉了SkyDNS中的etcd存储,将DNS记录直接保存在内存中,以提高查询性能。
kubedns容器监控Kubernetes中Service资源的变化,根据Service的名称和IP地址生成DNS记录,并将DNS记录保存在内存中;
dnsmasq容器从kubedns中获取DNS记录,提供DNS缓存,为客户端容器应用提供DNS查询服务;
sidecar提供对kubedns和dnsmasq服务的健康检查功能。
图4.4展现了KubeDNS的总体架构。
从Kubernetes 1.11版本开始,Kubernetes集群的DNS服务由CoreDNS提供。
CoreDNS是CNCF基金会的一个项目,是用Go语言实现的高性能、插件式、易扩展的DNS服务端。
CoreDNS解决了KubeDNS的一些问题,例如dnsmasq的安全漏洞、externalName不能使用stubDomains设置,等等。
CoreDNS支持自定义DNS记录及配置upstream DNS Server,可以统一管理Kubernetes基于服务的内部DNS和数据中心的物理DNS。
CoreDNS没有使用多个容器的架构,只用一个容器便实现了KubeDNS内3个容器的全部功能。
图4.5展现了CoreDNS的总体架构
下面以CoreDNS为例说明Kubernetes集群DNS服务的搭建过程。

4.5.1 在创建DNS服务之前修改每个Node上kubelet的启动参数
修改每个Node上kubelet的启动参数,加上以下两个参数。
--cluster-dns=169.169.0.100:为DNS服务的ClusterIP地址。
--cluster-domain=cluster.local:为在DNS服务中设置的域名。
然后重启kubelet服务。

4.5.2 创建CoreDNS应用
在部署CoreDNS应用前,至少需要创建一个ConfigMap、一个Deployment和一个Service共3个资源对象。
在启用了RBAC的集群中,还可以设置ServiceAccount、ClusterRole、ClusterRoleBinding对CoreDNS容器进行权限设置。
关于RBAC的内容详见6.2.3节的说明。
ConfigMap“coredns”主要设置CoreDNS的主配置文件Corefile的内容,其中可以定义各种域名的解析方式和使用的插件。
示例如下(Corefile的详细配置说明参见4.5.4节):
apiVersion: v1
king: ConfigMap
metadata:
name: coredns
namespace: kube-system
labels:
addonmanager.kubernetes.io/mode: EnsureExists
data:
Corefile: |
cluster.local {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus: 9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
. {
cache 30
loadbalance
forward . /etc/resolv.conf
}
Deployment“coredns”主要设置CoreDNS容器应用的内容,示例如下。
其中,replicas副本的数量通常应该根据集群的规模和服务数量确定,如果单个CoreDNS进程不足以支撑整个集群的DNS查询,则可以通过水平扩展提高查询能力。
由于DNS服务是Kubernetes集群的关键核心服务,所以建议为其Deployment设置自动扩缩容控制器,自动管理其副本数量。
另外,对资源限制部分(CPU限制和内存限制)的设置也应根据实际环境进行调整:
apiVersion: apps/v1
king: Deployment
metadata:
name: coredns
namespace: kube-system
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.io/name: "CoreDNS"
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
selector:
matchLables:
k8s-app: kube-dns
template:
metadata:
labels:
k8s-app: kube-dns
annotations:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec:
priorityClassName: system-cluster-critical
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: coredns
image: coredns/coredns:1.3.1
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 170Mi
requests:
cpu: 100m
memory: 70Mi
args: [ "-conf","/etc/coredns/Corefile" ]
volumeMounts:
- name: config-volume
mountPath: /etc/ciredns
readOnly: true
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
- containerPort: 9153
name: metrics
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: 8080
schema: HTTP
initialDelaySeconds: 60
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 5
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_BIND_SERVICE
drop:
- all
readOnlyRootFilesystem: true
dnsPolicy: Default
volumes:
- name: config-volume
configMap:
name: coredns
items:
- key: Corefile
path: Corefile
Service“kube-dns”是DNS服务的配置,示例如下。
这个服务需要设置固定的ClusterIP,也需要将所有Node上的kubelet启动参数--cluster-dns设置为这个ClusterIP:
apiVersion: v1
king: Service
metadata:
name: kube-dns
namespace: kube-system
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.io/name: "CoreDNS"
annotations:
prometheus.io/port: "9153"
prometheus.io/scrape: "true"
spec:
selector:
k8s-app: kube-dns
clusterIP: 169.169.0.100
ports:
- name: dns
port: 53
protocol: UDP
- name: dns-tcp
port: 53
protocol: TCP
- name: metrics
port: 9153
protocol: TCP
通过kubectl create完成CoreDNS服务的创建:
# kubectl create -f coredns.yaml
查看RC、Pod和Service,确保容器成功启动:
# kubectl get deployment --namespace=kube-system
# kubectl get pods --namespace=kube-system
# kubectl get services --namespace=kube-system

4.5.3 服务名的DNS解析
接下来使用一个带有nslookup工具的Pod(busybox.yaml)来验证DNS服务能否正常工作:
apiVersion: v1
king: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- name: busybox
image: gcr.io/google_containers/busybox
command:
- sleep
- "3600"
运行kubectl create -f busybox.yaml即可完成创建。
在该容器成功启动后,通过kubectl exec <container_id> nslookup进行测试:
# kubectl exec busybox -- nslookup redis-master
可以看到,通过DNS服务器169.169.0.100成功找到了redis-master服务的IP地址:169.169.8.10。
如果某个Service属于不同的命名空间,那么在进行Service查找时,需要补充Namespace的名称,组合成完整的域名。
下面以查找kube-dns服务为例,将其所在的Namespace“kube-system”补充在服务名之后,用“.”连接为“kube-dns.kube-system”,即可查询成功:
# kubectl exec busybox -- nslookup kube-dns.kube-system
如果仅使用“kube-dns”进行查找,则会失败:
nslookup: can't resolve 'kube-dns'

4.5.4 CoreDNS的配置说明
CoreDNS的主要功能是通过插件系统实现的。CoreDNS实现了一种链式插件结构,将DNS的逻辑抽象成了一个个插件,能够灵活组合使用。
常用的插件如下:
loadbalance:提供基于DNS的负载均衡功能。
loop:检测在DNS解析过程中出现的简单循环问题。
cache:提供前端缓存功能。
health:对Endpoint进行健康检查。
kubernetes:从Kubernetes中读取zone数据。
etcd:从etcd读取zone数据,可以用于自定义域名记录。
file:从RFC1035格式文件中读取zone数据。
hosts:使用/etc/hosts文件或者其他文件读取zone数据,可以用于自定义域名记录。
auto:从磁盘中自动加载区域文件。
reload:定时自动重新加载Corefile配置文件的内容。
forward:转发域名查询到上游DNS服务器。
proxy:转发特定的域名查询到多个其他DNS服务器,同时提供到多个DNS服务器的负载均衡功能。
prometheus:为Prometheus系统提供采集性能指标数据的URL。
pprof:在URL路径/debug/pprof下提供运行时的性能数据。
log:对DNS查询进行日志记录。
errors:对错误信息进行日志记录。
在下面的示例中为域名“cluster.local”设置了一系列插件,包括errors、health、kubernetes、prometheus、forward、cache、loop、reload和loadbalance,
在进行域名解析时,这些插件将以从上到下的顺序依次执行:
cluster.local {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus: 9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
另外,etcd和hosts插件都可以用于用户自定义域名记录。
下面是使用etcd插件的配置示例,将以“.com”结尾的域名记录配置为从etcd中获取,并将域名记录保存在/skydns路径下:
{
etcd com {
path / skydns
endpoint http://192.168.18.3.:2379
upstream /etc/resolv.conf
}
cache 160 com
loadbalance
proxy . /etc/resolv.conf
}
如果用户在etcd中插入一条“10.1.1.1 mycompany.com”DNS记录:
# etcdctl put /skydns/com/mycompany '{"host":"10.1.1.1", "ttl":60}'
客户端应用就能访问域名“mycompany.com”了:
# nslookup mycompany.com
forward和proxy插件都可以用于配置上游DNS服务器或其他DNS服务器,当在CoreDNS中查询不到域名时,会到其他DNS服务器上进行查询。
在实际环境中,可以将Kubernetes集群外部的DNS纳入CoreDNS,进行统一的DNS管理。

4.5.5 Pod级别的DNS配置说明
除了使用集群范围的DNS服务(如CoreDNS),在Pod级别也能设置DNS的相关策略和配置。
在Pod的YAML配置文件中通过spec.dnsPolicy字段设置DNS策略,例如:
apiVersion: v1
king: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
dnsPolicy: Default
目前可以设置的DNS策略如下:
Default:继承Pod所在宿主机的DNS设置。
ClusterFirst:优先使用Kubernetes环境的DNS服务(如CoreDNS提供的域名解析服务),将无法解析的域名转发到从宿主机继承的DNS服务器。
ClusterFirstWithHostNet:与ClusterFirst相同,对于以hostNetwork模式运行的Pod,应明确指定使用该策略。
None:忽略Kubernetes环境的DNS配置,通过spec.dnsConfig自定义DNS配置。
这个选项从Kubernetes 1.9版本开始引入,到Kubernetes 1.10版本升级为Beta版,到Kubernetes 1.14版本升级为稳定版。
自定义DNS配置可以通过spec.dnsConfig字段进行设置,可以设置下列信息。
nameservers:一组DNS服务器的列表,最多可以设置3个。
searches:一组用于域名搜索的DNS域名后缀,最多可以设置6个。
options:配置其他可选DNS参数,例如ndots、timeout等,以name或name/value对的形式表示。
以下面的dnsConfig为例:
apiVersion: v1
king: Pod
metadata:
name: dns-example
namespace: default
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- nsl.svc.cluster.local
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name: edns0
该Pod被成功创建之后,容器内的DNS配置文件/etc/resolv.conf的内容将被系统设置为:
nameserver 1.2.3.4
search nsl.svc.cluster.local my.dns.search.suffix
options ndots:2 edns0
表示该Pod完全使用自定义的DNS配置,不再使用Kubernetes环境的DNS服务。

4.6 Ingress:HTTP 7层路由机制
根据前面对Service的使用说明,我们知道Service的表现形式为IP:Port,即工作在TCP/IP层。
而对于基于HTTP的服务来说,不同的URL地址经常对应到不同的后端服务或者虚拟服务器(Virtual Host),
这些应用层的转发机制仅通过Kubernetes的Service机制是无法实现的。
从Kubernetes 1.1版本开始新增Ingress资源对象,用于将不同URL的访问请求转发到后端不同的Service,以实现HTTP层的业务路由机制。
Kubernetes使用了一个Ingress策略定义和一个具体的Ingress Controller,两者结合并实现了一个完整的Ingress负载均衡器。
使用Ingress进行负载分发时,Ingress Controller基于Ingress规则将客户端请求直接转发到Service对应的后端Endpoint(Pod)上,
这样会跳过kube-proxy的转发功能,kube-proxy不再起作用。
如果Ingress Controller提供的是对外服务,则实际上实现的是边缘路由器的功能。
图4.6显示了一个典型的HTTP层路由的例子。
其中:
对http://mywebsite.com/api的访问将被路由到后端名为api的Service;
对http://mywebsite.com/web的访问将被路由到后端名为web的Service;
对http://mywebsite.com/docs的访问将被路由到后端名为docs的Service。
为使用Ingress,需要创建Ingress Controller(带一个默认backend服务)和Ingress策略设置来共同完成。
下面通过一个例子分三步说明Ingress Controller和Ingress策略的配置方法,以及客户端如何访问Ingress提供的服务。

4.6.1 创建Ingress Controller和默认的backend服务
在定义Ingress策略之前,需要先部署Ingress Controller,以实现为所有后端Service都提供一个统一的入口。
Ingress Controller需要实现基于不同HTTP URL向后转发的负载分发规则,并可以灵活设置7层负载分发策略。
如果公有云服务商能够提供该类型的HTTP路由LoadBalancer,则也可设置其为Ingress Controller。
在Kubernetes中,Ingress Controller将以Pod的形式运行,监控API Server的/ingress接口后端的backend services,
如果Service发生变化,则Ingress Controller应自动更新其转发规则。
下面的例子使用Nginx来实现一个Ingress Controller,需要实现的基本逻辑如下。
(1)监听API Server,获取全部Ingress的定义。
(2)基于Ingress的定义,生成Nginx所需的配置文件/etc/nginx/nginx.conf。
(3)执行nginx -s reload命令,重新加载nginx.conf配置文件的内容。
本例使用谷歌提供的nginx-ingress-controller镜像来创建Ingress Controller。
该Ingress Controller以daemonset(nginx-ingress-daemonset.yaml)的形式进行创建,在每个Node上都将启动一个Nginx服务。
这里为Nginx容器设置了hostPort,将容器应用监听的80和443端口号映射到物理机上,
使得客户端应用可以通过URL地址“http://物理机IP:80”或“https://物理机IP:443”来访问该Ingress Controller。
这使得Nginx类似于通过NodePort映射到物理机的Service,成为代替kube-proxy的HTTP层的Load Balancer:
apiVersion: extensions/v1beta1
king: DaemonSet
metadata:
name: nginx-ingress-lb
labels:
name: nginx-ingress-lb
namespace: kube-system
spec:
template:
metadata:
labels:
name: nginx-ingress-lb
spec:
terminationGracePeriodSeconds: 60
containers:
- name: nginx-ingress-lb
image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 1
ports:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http_backend
为了让Ingress Controller正常启动,还需要为它配置一个默认的backend,用于在客户端访问的URL地址不存在时,返回一个正确的404应答。
这个backend服务用任何应用实现都可以,只要满足对根路径“/”的访问返回404应答,并且提供/healthz路径以使kubelet完成对它的健康检查。
另外,由于Nginx通过default-backend-service的服务名称(Service Name)去访问它,所以需要DNS服务正确运行:
default-backend.yaml
apiVersion: extensions/v1beta1
king: Deployment
metadata:
name: default-http-backend
labels:
k8s-app: default-http-backend
namespace: kube-system
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
image: gcr.io/google_containers/defaultbackend:1.0
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 1
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
-----
apiVersion: v1
king: Service
metadata:
name: default-http-backend
labels:
k8s-app: default-http-backend
namespace: kube-system
spec:
ports:
- port: 80
targetPort: 8080
selector:
k8s-app: default-http-backend
通过kubectl create命令创建backend服务:
# kubectl create -f default-backend.yaml
创建nginx-ingress-controller:
# kubectl create -f nginx-ingress-daemonset.yaml:
查看default-http-backend 和nginx-ingress-controller容器是否正确运行:
# kubectl get pod --namespace=kube-system
用curl访问任意Node的80端口号,验证nginx-ingress-controller和default-http-backend服务正常工作:
# curl k8s-node-2

4.6.2 定义Ingress策略
本例对mywebsite.com网站的访问设置Ingress策略,定义对其/demo路径的访问转发到后端webapp Service的规则:
ingress.yaml
apiVersion: extensions/v1beta1
king: Ingress
metadata:
name: mywebsite-ingress
spec:
rules:
- host: mywebsite.com
http:
paths:
- path: /demo
backend:
serviceName: webapp
servicePort: 8080
这个Ingress的定义,说明对目标地址http://mywebsite.com/demo的访问将被转发到集群中的Service webapp即webapp:8080/demo上。
在Ingress生效之前,需要先将webapp服务部署完成。
同时需要注意Ingress中path的定义,需要与后端真实Service提供的path一致,否则将被转发到一个不存在的path上,引发错误。
这里以第1章的例子为例,假设myweb服务已经部署完毕并正常运行,myweb提供的Web服务的路径也为/demo。
创建该Ingress:
# kubectl create -f ingress.yaml
# kubectl get ingress -o wide
在成功创建该Ingress后,查看其ADDRESS列,
如果显示了所有nginx-ingress-controller Pod的IP地址,则表示Nginx已经设置好后端Service的Endpoint,该Ingress可以正常工作了。
如果ADDRESS列为空,则通常说明Nginx未能正确连接到后端Service,需要排错。
登录任一nginx-ingress-controller Pod,查看其自动生成的nginx.conf配置文件的内容,可以看到对mywebsite.com/demo的转发规则的正确配置。

4.6.3 客户端访问http://mywebsite.com/demo
由于Ingress Controller容器通过hostPort将服务端口号80映射到了所有Node上,所以客户端可以通过任意Node访问mywebsite.com提供的服务。
需要说明的是,客户端只能通过域名mywebsite.com访问服务,这时要求客户端或者DNS将mywebsite.com域名解析到后端多个Node的真实IP地址上。
通过curl访问mywebsite.com提供的服务,可以得到myweb服务返回的网页内容。
可以用--resolve参数模拟DNS解析,目标地址为域名;也可以用-H 'Host:mywebsite.com'参数设置在HTTP头中要访问的域名,目标地址为IP地址
# curl --resolve mywebsite.com:80:192.168.18.3 http://mywebsite.com/demo

# curl -H 'Host:mywebsite.com' http://mywebsite.com/demo
如果通过浏览器访问,那么需要先在本机上设置域名mywebsite.com对应的IP地址,再到浏览器上进行访问。
以Windows为例,修改C:WindowsSystem32driversetchosts文件,加入一行记录:
192.168.18.3 mywebsite.com
然后在浏览器的地址栏输入http://mywebsite.com/demo/,就能够访问Ingress提供的服务了,如图4.7所示。

4.6.4 Ingress的策略配置技巧
为了实现灵活的负载分发策略,Ingress策略可以按多种方式进行配置,下面对几种常见的Ingress转发策略进行说明。

1.转发到单个后端服务上
基于这种设置,客户端到Ingress Controller的访问请求都将被转发到后端的唯一Service上,在这种情况下Ingress无须定义任何rule。
通过如下所示的设置,对Ingress Controller的访问请求都将被转发到“myweb:8080”这个服务上。
ingress-backend.yaml
apiVersion: extensions/v1beta1
king: Ingress
metadata:
name: mywebsite-ingress
spec:
backend:
serviceName: myweb
servicePort: 8080

2.同一域名下,不同的URL路径被转发到不同的服务上
这种配置常用于一个网站通过不同的路径提供不同的服务的场景,
例如/web表示访问Web页面,/api表示访问API接口,对应到后端的两个服务,通过Ingress的设置很容易就能将基于URL路径的转发规则定义出来。
通过如下所示的设置,
对“mywebsite.com/web”的访问请求将被转发到“web-service:80”服务上;
对“mywebsite.com/api”的访问请求将被转发到“api-service:8081”服务上:
ingress-rules.yaml
apiVersion: extensions/v1beta1
king: Ingress
metadata:
name: mywebsite-ingress
spec:
rules:
- host: mywebsite.com
http:
paths:
- path: /web
backend:
serviceName: web-service
servicePort: 80
- path: /api
backend:
serviceName: api-service
servicePort: 8081

3.不同的域名(虚拟主机名)被转发到不同的服务上
这种配置常用于一个网站通过不同的域名或虚拟主机名提供不同服务的场景,
例如foo.bar.com域名由service1提供服务,bar.foo.com域名由service2提供服务。
通过如下所示的设置,
对“foo.bar.com”的访问请求将被转发到“service1:80”服务上,
对“bar.foo.com”的访问请求将被转发到“service2:80”服务上:
ingress-hosts.yaml
apiVersion: extensions/v1beta1
king: Ingress
metadata:
name: mywebsite-ingress
spec:
rules:
- host: foo.bar.com
http:
paths:
- backend:
serviceName: service1
servicePort: 80
- host: bar.foo.com
http:
paths:
- backend:
serviceName: service2
servicePort: 80

4.不使用域名的转发规则
这种配置用于一个网站不使用域名直接提供服务的场景,此时通过任意一台运行ingress-controller的Node都能访问到后端的服务。
以上节的后端服务webapp为例,下面的配置为将“<ingress-controller-ip>/demo”的访问请求转发到“webapp:8080/demo”服务上:
ingress-no-host.yaml
apiVersion: extensions/v1beta1
king: Ingress
metadata:
name: mywebsite-ingress
spec:
rules:
http:
paths:
- path: /demo
backend:
serviceName: webapp
servicePort: 8080
注意,使用无域名的Ingress转发规则时,将默认禁用非安全HTTP,强制启用HTTPS。
例如,当使用Nginx作为Ingress Controller时,在其配置文件/etc/nginx/nginx.conf中将会自动设置下面的规则,将全部HTTP的访问请求直接返回301错误:
......
if ($pass_access_scheme = http) {
return 301 https://$best_http_host$request_uri;
}
......
客户端使用HTTP访问将得到301的错误应答:
# curl http://192.168.18.3/demo
使用HTTPS能够访问成功:
# curl -k https://192.168.18.3/demo
可以在Ingress的定义中设置一个annotation“ingress.kubernetes.io/ssl-redirect=false”来关闭强制启用HTTPS的设置:
ingress-http.yaml
apiVersion: extensions/v1beta1
king: Ingress
metadata:
name: mywebsite-ingress
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
http:
paths:
- path: /demo
backend:
serviceName: webapp
servicePort: 8080
这样,到Ingress Controller的访问就可以使用HTTP了:
# curl http://192.168.18.3/demo

4.6.5 Ingress的TLS安全设置
为了Ingress提供HTTPS的安全访问,可以为Ingress中的域名进行TLS安全证书的设置。
设置的步骤如下:
(1)创建自签名的密钥和SSL证书文件。
(2)将证书保存到Kubernetes中的一个Secret资源对象上。
(3)将该Secret对象设置到Ingress中。
根据提供服务的网站域名是一个还是多个,可以使用不同的操作完成前两步SSL证书和Secret对象的创建,在只有一个域名的情况下设置相对简单。
第3步对于这两种场景来说是相同的。
对于只有一个域名的场景来说,可以通过OpenSSL工具直接生成密钥和证书文件,将命令行参数-subj中的/CN设置为网站域名:
# openssl req -x509 -nodes -days 5000 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=mywebsite.com"
上述命令将生成tls.key和tls.crt两个文件。
然后根据tls.key和tls.crt文件创建secret资源对象,有以下两种方法。
方法一:使用kubectl create secret tls命令直接通过tls.key和tls.crt文件创建secret对象。
# kubectl create secret tls mywebsite-ingress-secret --key tls.key --cert tls.crt
方法二:编辑mywebsite-ingress-secret.yaml文件,将tls.key和tls.crt文件的内容复制进去,使用kubectl create命令进行创建。
apiVersion: v1
king: Secret
metadata:
name: mywebsite-ingress-secret
type: kubernetes.io/tls
data:
tls.crt: xxxxxxxxxxxxxxx
tls.key: xxxxxxxxxxxxxx
# kubectl create -f mywebsite-ingress-secret.yaml
如果提供服务的网站不止一个域名,例如前面第3种Ingress策略配置方式,
则SSL证书需要使用额外的一个x509 v3配置文件辅助完成,在[alt_names]段中完成多个DNS域名的设置。
首先编写openssl.cnf文件,内容为:
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = mywebsite.com
DNS.2 = mywebsite2.com
接着使用OpenSSL工具完成密钥和证书的创建。
生成自签名CA证书:
# openssl genrsa -out ca.key 2048
# openssl req -x509 -new -nodes -key ca.key -days 5000 -out ca.crt -subj "/CN=mywebsite.com"
基于openssl.cnf和CA证书生成ingress SSL证书:
# openssl genrsa -out ingress.key 2048
# openssl req -new -key ingress.key -out ingress.csr -subj "/CN=mywebsite.com" -config openssl.cnf
# openssl x509 -req -in ingress.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ingress.crt -days 5000 -extensions v3_req -extfile openssl.cnf
然后根据ingress.key和ingress.crt文件创建secret资源对象,
同样可以通过kubectl create secret tls命令或YAML配置文件生成。
# kubectl create secret tls mywebsite-ingress-secret --key ingress.key --cert ingress.crt
至此,Ingress的TLS证书就被成功创建到Secret对象中了。
下面创建Ingress对象,在tls段引用刚刚创建好的Secret对象:
ingress-tls.yaml
apiVersion: extensions/v1beta1
king: Ingress
metadata:
name: mywebsite-ingress-tls
spec:
tls:
- hosts:
- mywebsite.com
secretName: mywebsite-ingress-secret
rules:
- hosts: mywebsite.com
http:
paths:
- path: /demo
backend:
serviceName: webapp
servicePort: 8080
之后,就可以通过HTTPS来访问mywebsite.com了。
# curl -H 'Host:mywebsite.com' -k https://192.168.18.3/demo/:
如果是通过浏览器访问的,则在浏览器的地址栏输入https://mywebsite.com/demo/来访问Ingress提供的服务,浏览器会提示不安全。
单击“继续前往mywebsite.com(不安全)”标签,访问后可看到Ingress后端服务提供的页面,如图4.9所示。

原文地址:https://www.cnblogs.com/BradMiller/p/12228250.html