k8s中内置了dns和服务发现,但这些服务都是pod内部的机制,明显不能与外界通讯,为了与外界通讯,可以通过nodeport或其他方式把容器的端口映射到主机上。
内置服务service的举例
以deployment为例,创建一个deployment的容器集合。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostnames
spec:
selector:
matchLabels:
app: hostnames
replicas: 3
template:
metadata:
labels:
app: hostnames
spec:
containers:
- name: hostnames
image: k8s.gcr.io/serve_hostname
ports:
- containerPort: 9376
protocol: TCP
这个deployment表示,使用 k8s.gcr.io/serve_hostname这个image创建3个replica的pods,每个pod中的9376端口负责服务的发布,其实这个应用就是获得本机的hostname。
此时系统产生了3个pods,如下:
根据deployment定义的app=hostnames这个label,可以查看到这个deployment创建的pod如下:
root@k8s-master:~/services# kubectl get pods -l app=hostnames -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hostnames-5f9c957df4-8lgbh 1/1 Running 0 4d16h 10.10.2.3 k8s-worker02 <none> <none>
hostnames-5f9c957df4-pxdjn 1/1 Running 0 4d16h 10.10.1.4 k8s-worker01 <none> <none>
hostnames-5f9c957df4-vdp87 1/1 Running 0 4d16h 10.10.1.3 k8s-worker01 <none> <none>
三个pod已经创建完毕,可以通过curl ip:9376端口访问到pod中运行的服务。
root@k8s-master:~/services# curl 10.10.1.3:9376
hostnames-5f9c957df4-vdp87
此时说明这个deployment已经顺利部署。
但是这三个pod的服务还是分离的,如何实现集中,此时就要用到service这个对象。
service负责以cluster ip或者headless的方式实现服务的集中注册与对外呈现,这就避免了以pod ip为服务端口的劣势,一旦pod发生变化,如重启或重建,pod ip都会发生变化,而service的方式则在pod之上抽象出一层服务对象。
基于上述三个pod,分别运行了hostnames的服务,用一个service可以把他们串联起来。
apiVersion: v1
kind: Service
metadata:
name: hostnames
spec:
selector:
app: hostnames
ports:
- name: default
protocol: TCP
port: 80
targetPort: 9376
这是一个services,它定义了selector app=hostnames,这就创建了一个独立于pod的service,用80端口对外提供服务,而pods们的9376端口都被代理到这个服务上了。
root@k8s-master:~/services# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demo ClusterIP 10.20.87.120 <none> 80/TCP 15h
hostnames ClusterIP 10.20.214.128 <none> 80/TCP 4d16h
kube-node-service NodePort 10.20.156.215 <none> 80:31777/TCP 3d5h
kubernetes ClusterIP 10.20.0.1 <none> 443/TCP 5d16h
root@k8s-master:~/services# kubectl get svc hostnames -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
hostnames ClusterIP 10.20.214.128 <none> 80/TCP 4d16h app=hostnames
此时可以明确看到,hostnames的service,他的cluster ip也就是VIP是10.20.214.128,他对pod之间的网络开放80端口访问。根据这个cluster ip进行访问,三次返回不同的hostname,说明cluster ip以vip的方式实现了负载的均衡。
root@k8s-master:~/services# curl 10.20.214.128:80
hostnames-5f9c957df4-pxdjn
root@k8s-master:~/services# curl 10.20.214.128:80
hostnames-5f9c957df4-vdp87
root@k8s-master:~/services# curl 10.20.214.128:80
hostnames-5f9c957df4-8lgbh
而其他pod可以连接这个service实现对负载均衡的三个服务的访问。这就是service这个内置服务的完整过程。
外部服务访问
上述的服务发现仅仅局限于pod内部和pod之间,这些地址对外边的hosts不可见,需要一层串透才能被发现。
通过deployment创建一个app=demo的一个pod,这个pod中运行http 80端口的服务。
root@k8s-master:~# kubectl create deployment demo --image=httpd --port=80
root@k8s-master:~# kubectl get pods -l app=demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-654c477f6d-spvvk 1/1 Running 0 16h 10.10.2.11 k8s-worker02 <none> <none>
此时这个pod正常访问80端口。
此时把服务expose出去,就产生了cluster ip的服务
root@k8s-master:~# kubectl expose deployment demo
root@k8s-master:~# kubectl get svc -l app=demo -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
demo ClusterIP 10.20.87.120 <none> 80/TCP 16h app=demo
以上全是内部的。
如果想让这个pod的80端口被其他外部主机访问,这个简单,需要创建一个ingress service,使用node port的方式把80端口的内部服务,映射到host主机所在的80端口,这个服务定义如下:
apiVersion: v1
kind: Service
metadata:
name: kube-node-service
labels:
name: kube-node-service
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app: demo
这个服务定义产生了内置的service以nodeport的方式对外服务,通过nodeselector,作用于app=demo的pod的应用。
root@k8s-master:~# kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default demo ClusterIP 10.20.87.120 <none> 80/TCP 16h
default hostnames ClusterIP 10.20.214.128 <none> 80/TCP 4d17h
default kube-node-service NodePort 10.20.156.215 <none> 80:31777/TCP 3d5h
root@k8s-master:~# kubectl get ep -o wide
NAME ENDPOINTS AGE
demo 10.10.2.11:80 16h
hostnames 10.10.1.3:9376,10.10.1.4:9376,10.10.2.3:9376 4d17h
kube-node-service 10.10.2.11:80 3d5h
kubernetes 192.168.1.11:6443 5d16h
此时表示这个service通过31777的端口对外服务了。
访问worker01、worker02的主机执行测试,发现worker节点上的31777端口定向到了容器中,实现了容器服务对外发布。
root@k8s-master:~# curl 192.168.1.12:31777
<html><body><h1>It works!</h1></body></html>
root@k8s-master:~# curl 192.168.1.13:31777
<html><body><h1>It works!</h1></body></html>
为什么要用ingress
上述nodeport的方式固然不错,但是集群复杂,服务越来越多,通过维护端口的方式反而不利于服务的统一管理。
每个lb都需要一个公网ip,如果每个service都需要一个lb对应,会需要很多的公网ip,而ingress相当于lb -> ingress – 多个service。此时就出现了ingress的概念,ingress对外服务的端口集合,就是“服务”的“服务”。
这种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的 Ingress 服务。
所以,Ingress 的功能其实很容易理解:所谓 Ingress,就是 Service 的“Service”。举个例子,假如我现在有这样一个站点:https://cafe.example.com。其中,https://cafe.example.com/coffee,对应的是“咖啡点餐系统”。而,https://cafe.example.com/tea,对应的则是“茶水点餐系统”。这两个系统,分别由名叫 coffee 和 tea 这样两个 Deployment 来提供服务。那么现在,我如何能使用 Kubernetes 的 Ingress 来创建一个统一的负载均衡器,从而实现当用户访问不同的域名时,能够访问到不同的 Deployment 呢?
上述功能,在 Kubernetes 里就需要通过 Ingress 对象来描述,如下所示:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress
spec:
tls:
- hosts:
- cafe.example.com
secretName: cafe-secret
rules:
- host: cafe.example.com
http:
paths:
- path: /tea
backend:
serviceName: tea-svc
servicePort: 80
- path: /coffee
backend:
serviceName: coffee-svc
servicePort: 80
在上面这个名叫 cafe-ingress.yaml 文件中,最值得我们关注的,是 rules 字段。
在 Kubernetes 里,这个字段叫作:IngressRule。IngressRule 的 Key,就叫做:host。它必须是一个标准的域名格式(Fully Qualified Domain Name)的字符串,而不能是 IP 地址。备注:Fully Qualified Domain Name 的具体格式,可以参考RFC 3986标准。而 host 字段定义的值,就是这个 Ingress 的入口。
这也就意味着,当用户访问 cafe.example.com 的时候,实际上访问到的是这个 Ingress 对象。这样,Kubernetes 就能使用 IngressRule 来对你的请求进行下一步转发。而接下来 IngressRule 规则的定义,则依赖于 path 字段。你可以简单地理解为,这里的每一个 path 都对应一个后端 Service。
所以在我们的例子里,我定义了两个 path,它们分别对应 coffee 和 tea 这两个 Deployment 的 Service(即:coffee-svc 和 tea-svc)。
通过上面的讲解,不难看到,所谓 Ingress 对象,其实就是 Kubernetes 项目对“反向代理”的一种抽象。一个 Ingress 对象的主要内容,实际上就是一个“反向代理”服务(比如:Nginx)的配置文件的描述。而这个代理服务对应的转发规则,就是 IngressRule。这就是为什么在每条 IngressRule 里,需要有一个 host 字段来作为这条 IngressRule 的入口,然后还需要有一系列 path 字段来声明具体的转发策略。这其实跟 Nginx、HAproxy 等项目的配置文件的写法是一致的。而有了 Ingress 这样一个统一的抽象,Kubernetes 的用户就无需关心 Ingress 的具体细节了。在实际的使用中,你只需要从社区里选择一个具体的 Ingress Controller,把它部署在 Kubernetes 集群里即可。然后,这个 Ingress Controller 会根据你定义的 Ingress 对象,提供对应的代理能力。目前,业界常用的各种反向代理项目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。
所以重点在于,你需要在集群中部署ingress controller这个反向代理,这样内部的svc通过ingress就可以通向外部了。
我在我的3节点k8s集群上安装部署了ingress controller如下:
root@k8s-master:~# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/baremetal/deploy.yaml
root@k8s-master:~# POD_NAMESPACE=ingress-nginx
root@k8s-master:~# POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o name)
kubectl exec $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version
-------------------------------------------------------------------------------
NGINX Ingress controller
Release: v1.1.0
Build: cacbee86b6ccc45bde8ffc184521bed3022e7dee
Repository: https://github.com/kubernetes/ingress-nginx
nginx version: nginx/1.19.9
查看ingress-nginx创建的pod如下:
root@k8s-master:~# kubectl get pods -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-admission-create--1-xdwbd 0/1 Completed 0 2d17h 10.10.1.10 k8s-worker01 <none> <none>
ingress-nginx-admission-patch--1-r4znz 0/1 Completed 2 2d17h 10.10.2.9 k8s-worker02 <none> <none>
ingress-nginx-controller-7b8bdb996c-ptl42 1/1 Running 0 120m 192.168.1.12 k8s-worker01 <none> <none>
此时说明服务正常启动了。
这是controller已经创建好,只需要按照应用创建ingress就可以了。
root@k8s-master:~# kubectl create ingress demo-localhost --class=nginx --rule=demo.localdev.me/*=demo:80
root@k8s-master:~# kubectl get ingress -o wide
NAME CLASS HOSTS ADDRESS PORTS AGE
demo-localhost nginx demo.localdev.me 192.168.1.12 80 16h
说明这个demo.localdev.me就对应主机192.168.1.12上的80端口,实现外部访问,这是k8s集群的worker01
把demo.localdev.me写入到所在k8s master服务器的host文件中,master可以解析这个主机名,对应192.168.1.12 这台host。
root@k8s-master:~# curl demo.localdev.me
<html><body><h1>It works!</h1></body></html>
看似简单,其实这个ingress是定义了demo.localdev.me和demo:80服务之间的对应关系
root@k8s-master:~# kubectl describe ing demo-localhost
Name: demo-localhost
Namespace: default
Address: 192.168.1.12
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
demo.localdev.me
/ demo:80 (10.10.2.11:80)
Annotations: <none>
Events: <none>
demo这个服务如下所示:
root@k8s-master:~# kubectl get svc -l app=demo -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
demo ClusterIP 10.20.87.120 <none> 80/TCP 17h app=demo
正是这个demo:80端口,被ingress-nginx-controller反向代理,发布到外部网络。
此处为什么这个ingress入口在192.168.1.12呢?这还要从ingress controller说起,这个controller运行在worker01上。
root@k8s-master:~# kubectl get pods -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-admission-create--1-xdwbd 0/1 Completed 0 2d17h 10.10.1.10 k8s-worker01 <none> <none>
ingress-nginx-admission-patch--1-r4znz 0/1 Completed 2 2d17h 10.10.2.9 k8s-worker02 <none> <none>
ingress-nginx-controller-7b8bdb996c-ptl42 1/1 Running 0 136m 192.168.1.12 k8s-worker01 <none> <none>
所以被这个ingress controller代理的服务,都要从192.168.1.12(worker01)上通过外部访问。
默认ingress controllert通过随机产生的端口,如图所示的30664对外提供访问。
root@k8s-master:~# kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default demo ClusterIP 10.20.87.120 <none> 80/TCP 17h
default hostnames ClusterIP 10.20.214.128 <none> 80/TCP 4d18h
default kube-node-service NodePort 10.20.156.215 <none> 80:31777/TCP 3d6h
default kubernetes ClusterIP 10.20.0.1 <none> 443/TCP 5d17h
ingress-nginx ingress-nginx-controller NodePort 10.20.187.113 <none> 80:30664/TCP,443:32661/TCP 2d17h
ingress-nginx ingress-nginx-controller-admission ClusterIP 10.20.48.170 <none> 443/TCP 2d17h
kube-system kube-dns ClusterIP 10.20.0.10 <none> 53/UDP,53/TCP,9153/TCP 5d17h
但是如果想修改ingress controller的端口,定向到80端口怎么办呢?这就要修改ingress controller的pod配置文件。
root@k8s-master:~# kubectl edit deployment ingress-nginx-controller -n ingress-nginx
加上这一句:hostNetwork: true,这样就使用host的网络了,也就是192.168.1.12的80端口。

至此ingress controller就配置完毕,也就可以理解,集群中必须先有ingress controller,然后才能创建ingress服务。
这个ingress服务创建的时候,写了什么东西给controller呢?
root@k8s-master:~# kubectl create ingress demo-localhost --class=nginx --rule=demo.localdev.me/*=demo:80
其实这一句话,在ingress controller看来,就是在controller这个nginx的配置文件中写入了server和这个demo:80的service之间的对应关系,把这个代理关系写入到nginx的配置文件。
root@k8s-master:~# kubectl -n ingress-nginx exec ingress-nginx-controller-7b8bdb996c-ptl42 -- cat /etc/nginx/nginx.conf
这个文件中,在start server demo.localdev.me和end server demo.localdev.me
之间创建了一个nginx的server,代理了demo:80这个服务。如果创建多个ingress,则会在这个controller上创建多个server,这就实现了服务的统一管理。
## start server demo.localdev.me
server {
server_name demo.localdev.me ;
listen 80 ;
listen [::]:80 ;
listen 443 ssl http2 ;
listen [::]:443 ssl http2 ;
set $proxy_upstream_name "-";
ssl_certificate_by_lua_block {
certificate.call()
}
location / {
set $namespace "default";
set $ingress_name "demo-localhost";
set $service_name "demo";
set $service_port "80";
set $location_path "/";
set $global_rate_limit_exceeding n;
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
## end server demo.localdev.me
以上部分内容参考文档:https://www.1024sou.com/article/217420.html
暂无评论