实验环境:
按照图示部署好了K8s集群,一个Master,两个worker nodes。
一、SVC概述:
所以Kubernetes Service定义了这样一种抽象:一个Pod的逻辑分组,一种可以访问它们的策略 ——通常称为微服务。这一组Pod能够被Service访问到,通常是通过Label Selector。用户仅需要访问 Service,Service能够提供负载均衡的能力,能够将用户流量分配到背后的pod上去。 Service能够提供负载均衡的能力,但是在使用上有以下限制:
只提供 4 层负载均衡能力,而没有 7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的。
二、没有SVC的情景,外部应用访问集群内的pods:
创建deployment,部署3个pods:
生成并修改deployment的yaml文件:将端口映射到宿主的33300
kubectl create deployment web1 --image=nginx --dry-run=client -o yaml >web1.yaml
[root@vms201 svc]
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: web1
name: web1
spec:
replicas: 2
selector:
matchLabels:
app: web1
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: web1
spec:
terminationGracePeriodSeconds: 0
containers:
- image: nginx
name: nginx
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
hostPort: 33300
resources: {}
status: {}
创建并查看deployment:
[root@vms201 svc]
deployment.apps/web1 created
[root@vms201 svc]
deployment.apps/web1 created
[root@vms201 svc]
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web1-7579ff4ffd-rmhmn 1/1 Running 0 5s 10.244.185.124 vms203.rhce.cc <none> <none>
web1-7579ff4ffd-xrzkn 1/1 Running 0 5s 10.244.58.249 vms202.rhce.cc <none> <none>
[root@vms201 svc]
NAME READY UP-TO-DATE AVAILABLE AGE
web1 2/2 2 2 10s
这种情况下,在外部设备访问两个worker nodes上的2个pod:
vms202: vms203: 这种情况虽然能够满足外部设备访问集群中的pod,但是需要修改对应的node的IP地址进行访问,不满足实际的需求。同时,如果一个node上有多个容器映射端口到宿主机的同一个端口号上,会出现端口冲突的故障。
三、创建SVC
部署SVC关联的deployment:
修改deploy的yaml文件并部署:
[root@vms201 svc]
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: web1
name: web1
spec:
replicas: 2
selector:
matchLabels:
app1: web1
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app1: web1
app2: web2
app3: web3
spec:
terminationGracePeriodSeconds: 0
containers:
- image: nginx
name: nginx
imagePullPolicy: IfNotPresent
ports:
resources: {}
status: {}
[root@vms201 svc]
deployment.apps/web1 created
[root@vms201 svc]
NAME READY STATUS RESTARTS AGE LABELS
web1-54cbfdf7b5-c5r9n 1/1 Running 0 5m49s app1=web1,app2=web2,app3=web3,pod -template-hash=54cbfdf7b5
web1-54cbfdf7b5-wg2vr 1/1 Running 0 5m49s app1=web1,app2=web2,app3=web3,pod -template-hash=54cbfdf7b5
yaml文件中定义了2个副本,并且每个pod有3个标签。
创建SVC的方式:
- 直接使用命令行;(推荐)
- 使用yaml文件的方式创建。
举例: 通过命令行的方式创建SVC(建议)
语法:
kubectl expose --name=名字 资源类型/名字 --port=xxx --target-port=yyy --selector=aaa=bbb
其中target-port是pod运行的端口,不能随便写。而—port为SVC的端口,可以随意修改。对deployment设置服务:
[root@vms201 svc]
service/svc1 exposed
[root@vms201 svc]
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
svc1 ClusterIP 10.105.219.0 <none> 80/TCP 63s app1=web1
可以看到SELECTOR为web1,说明了如果在创建SVC时没有指定哪个标签定位pod时,使用的是和deploy一样的标签。查看SVC关联的后端容器等信息:
[root@vms201 svc]
Name: svc1
Namespace: svc
Labels: app=web1
Annotations: <none>
Selector: app1=web1
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.105.219.0
IPs: 10.105.219.0
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.185.125:80,10.244.58.252:80
Session Affinity: None
Events: <none>
Endpoints指明了SVC关联的后端容器的地址。
注意:如果我们再deployment之外重新创建了一个pod,且标签和设置的SVC匹配的标签相同,也会被被SVC关联。
修改deployment中部署pod的主页,查看负载分担的情况:
kubectl exec -it web1-54cbfdf7b5-c5r9n -- sh -c "echo 111 > /usr/share/nginx/html/index.html"
kubectl exec -it web1-54cbfdf7b5-wg2vr -- sh -c "echo 222 > /usr/share/nginx/html/index.html"
svc的地址为 10.105.219.0,在cluster中都可以访问:
[root@vms201 svc]
> curl 10.105.219.0
> sleep 1
> done
111
111
222
111
111
222
111
111
222
111
222
可以看到流量被分配到了SVC关联的两个pod中去。
注意:kube-proxy的模式是iptales(默认),则无法ping通svc的地址,如果为ipvs,则可以ping通svc的地址。
[root@vms201 svc]
PING 10.105.219 (10.105.0.219) 56(84) bytes of data.
四、服务的发现
我们现在创建的应用比较单一,有时候需要创建多级应用,例如WordPress+mysql。流量访问Wordpress的SVC,被负载到身后的pods中;Wordpress的pod再连接到mysql的pod的SVC上,访问mysqldb。而wordpress如何访问mysql pods的SVC,则就是集群的发现。
服务发现的三种方式:
- clusterIP
- 变量方式
- DNS方法(推荐)
方式1: clusterIP
修改mysql的vim文件:
kubectl run dbpod --image=mysql --image-pull-policy=IfNotPresent --dry-run=client -o yaml > dbpod.yaml
[root@vms201 svc]
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: dbpod
name: dbpod
spec:
terminationGracePeriodSeconds: 0
containers:
- image: mysql
imagePullPolicy: IfNotPresent
name: dbpod
resources: {}
env:
- name: MYSQL_ROOT_PASSWORD
value: mysql1
- name: MYSQL_USER
value: tom
- name: MYSQL_PASWORD
value: mysql1
- name: MYSQL_DATABASE
value: wordpress
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
创建并查看mysql的pod:
[root@vms201 svc]
pod/dbpod created
[root@vms201 svc]
NAME READY STATUS RESTARTS AGE
dbpod 1/1 Running 0 6s
创建mysql pod对应的SVC:
[root@vms201 svc]
service/dbsvc exposed
[root@vms201 svc]
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dbsvc ClusterIP 10.111.190.70 <none> 3306/TCP 27s
创建wordpress的pod,yaml文件如下:
[root@vms201 svc]
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: blogpod
name: blogpod
spec:
terminationGracePeriodSeconds: 0
containers:
- image: wordpress
imagePullPolicy: IfNotPresent
name: blogpod
resources: {}
env:
- name: WORDPRESS_DB_HOST
value: 10.111.190.70
- name: WORDPRESS_DB_USER
value: root
- name: WORDPRESS_DB_PASSWORD
value: mysql1
- name: WORDPRESS_DB_NAME
value: wordpress
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
其中WORDPRESS_DB_HOST为mysql SVC的cluster IP,WORDPRESS_DB_USER、WORDPRESS_DB_PASSWORD、WORDPRESS_DB_NAME分别是mysqldb的账号密码和数据库。
[root@vms201 svc]
pod/blogpod created
[root@vms201 svc]
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
blogpod 1/1 Running 0 2m23s 10.244.185.66 vms203.rhce.cc <none> <none>
dbpod 1/1 Running 0 8m10s 10.244.185.126 vms203.rhce.cc <none> <none>
可以看到,blogpod运行在vms203.rhce.cc宿主机上。接着创建wordpress的SVC:
[root@vms201 svc]
service/blogsvc exposed
[root@vms201 svc]
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
blogsvc NodePort 10.105.201.27 <none> 80:32726/TCP 5s
dbsvc ClusterIP 10.111.190.70 <none> 3306/TCP 8m21s
type=NodePort会在后续补充。现在可以使用宿主机vms203.rhce.cc的32726端口访问到wordpress,发现不用创建数据库,说明连接成功。
方式2: 变量方式
修改blogpod的变量:blogpod会以变量的方式去记录服务的信息。进入pod,输入命令env查看环境变量:
[root@vms201 svc]
root@blogpod:/var/www/html
DBSVC_SERVICE_HOST=10.111.190.70
KUBERNETES_SERVICE_HOST=10.96.0.1
root@blogpod:/var/www/html
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
DBSVC_SERVICE_PORT=3306
其中的几个参数需要注意:
- 大写服务名_SERVICE_HOST=服务的IP
- 大写服务名_SERVICE_PORT=服务的端口
- DBSVC_SERVICE_HOST=dbsec的IP
- DBSVC_SERVICE_PORT=dbsec的端口
相关SVC信息如果哎此pod之前创建,会自动被记录,在之后则不会。
重新创建blogpod:
[root@vms201 svc]
pod "blogpod" deleted
修改blogpod的yaml文件:引用为变量的方式。
[root@vms201 svc]
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: blogpod
name: blogpod
spec:
terminationGracePeriodSeconds: 0
containers:
- image: wordpress
imagePullPolicy: IfNotPresent
name: blogpod
resources: {}
env:
- name: WORDPRESS_DB_HOST
value: $(DBSVC_SERVICE_HOST)
- name: WORDPRESS_DB_USER
value: root
- name: WORDPRESS_DB_PASSWORD
value: mysql1
- name: WORDPRESS_DB_NAME
value: wordpress
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
同理可以访问到WordPress上,且不用创建数据库。
此方式存在的问题:
- 只能获取相同namespace里的变量;
- 变量的获取有先后顺序,引用的变量必须要先创建。
方式3: DNS方式
直接通过SVC名称的方式访问服务。可以看到集群中已经存在了一个DNS的SVC:kube-dns 。
[root@vms201 svc]
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 7d17h k8s-app=kube-dns
metrics-server ClusterIP 10.96.163.225 <none> 443/TCP 7d15h k8s-app=metrics-server
当我们每创建一个SVC,都会想kube-dns 进行注册,kubedns都会知道这个这个SVC的IP地址。所以在同一个命名空间里面可以直接通过SVC名访问SVC(通过这个SVC名向kube-dns进行查询),如果pod和SVC不是同一个命名空间,需要加上指定SVC命名空间的参数。
重新创建pod,修改WORDPRESS_DB_HOST参数为dbsvc即可:
[root@vms201 svc]
pod "blogpod" deleted
[root@vms201 svc]
[root@vms201 svc]
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: blogpod
name: blogpod
spec:
terminationGracePeriodSeconds: 0
containers:
- image: wordpress
imagePullPolicy: IfNotPresent
name: blogpod
resources: {}
env:
- name: WORDPRESS_DB_HOST
value: dbsvc
- name: WORDPRESS_DB_USER
value: root
- name: WORDPRESS_DB_PASSWORD
value: mysql1
- name: WORDPRESS_DB_NAME
value: wordpress
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
[root@vms201 svc]
pod/blogpod created
同理可以访问到WordPress上,且不用创建数据库。
五、服务的发布
所谓发布指的是,如何让集群之外的主机能访问服务。Service发布方式在 K8s 中有以下四种类型:
1.ClusterIp: 默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP(service创建一个仅集群内部可访问的ip,集群内部其他的pod可以通过该服务访问到其监控下的pod) 2.NodePort: 在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过NodePort 来访问该服务(在service及各个node节点上开启端口,外部的应用程序或客户端访问node的端口将会转发到service的端口,而service将会依据负载均衡随机将请求转发到某一个pod的端口上。一般暴露服务常用的端口)。NodePort是宿主机的端口,port是Service的端口,target-port为pod的端口。 创建deploy和服务,type内心为NodePort:
[root@vms201 svc]
deployment.apps/web1 created
[root@vms201 svc]
service/blogsvc exposed
[root@vms201 svc]
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
websvc NodePort 10.103.97.15 <none> 80:32717/TCP 2m12s
可以看到,type为NodePort,映射的端口为32717,我们就可以在外部设备访问集群内的任何一个机器(无论是master还是worker nodes),以访问到内部的pod: 此方式存在的问题,例如在一个集群发布了多个SVC,那么其宿主机上也会映射相应的端,这样会导致2个问题:
- 端口越多,漏洞可能性越大;
- 维护起来也麻烦。
3.LoadBalancer: 在 NodePort 的基础上,借助cloud provider创建一个外部负载均衡器,并将请求转发到: NodePort(在NodePort基础之上,即各个节点前加入了负载均衡器实现了真正的高可用,一般云供应商提供的k8s集群就是这种) 每个SVC都有一个私有的地址,只能在集群内部访问,如果我们将一个SVC设置为Loadbalancer,则这个SVC会获取一个外部IP地址(需要一个地址池,由metallb提供)
安装metallb,官网:https://metallb.universe.tf/installation/,根据其中的步骤完成metalib安装。创建命名空间:
[root@vms201 svc]
namespace/metallb-system created
下载的yaml文件,地址为:https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml,下载完成后保存为metallb.yaml文件。修改其文件,在image下设置imagePullPolicy为IfNotPresent。
应用对于的yaml文件创建metallb的pod:
[root@vms201 svc]
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
role.rbac.authorization.k8s.io/controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
rolebinding.rbac.authorization.k8s.io/controller created
daemonset.apps/speaker created
deployment.apps/controller created
[root@vms201 svc]
NAME READY STATUS RESTARTS AGE
controller-6b78bff7d9-shv95 1/1 Running 0 42m
speaker-gcbkh 1/1 Running 0 42m
speaker-tg2tm 1/1 Running 0 42m
speaker-v5f7h 1/1 Running 0 42m
创建地组池,yaml文件如下:分配的地址为192.168.0.240-192.168.0.250,与node同网段。
[root@vms201 svc]
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.0.240-192.168.0.250
应用yaml文件:
[root@vms201 svc]
configmap/config created
删除之前的SVC,创建新的SVC:
[root@vms201 svc]
service "websvc" deleted
[root@vms201 svc]
service/websvc exposed
[root@vms201 svc]
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
websvc LoadBalancer 10.110.232.180 192.168.0.240 80:32010/TCP 45s
可以看到,EXTERNAL-IP为地组池分配的地址:192.168.0.240。如果没有安装metallib,服务的状态为pending。外部设备现在可以以192.168.0.240直接访问pod:
LB方式最大的缺点则是每个service一个LB又有点浪费和麻烦,,并且需要k8s之外的支持。
4.Ingress:(推荐) ingress(简称ing)则只需要一个NodePort或者一个LB就可以满足所有service对外服务的需求。工作机制大致可以用下图表示: 实际上,ingress相当于一个7层的负载均衡器,是k8s对反向代理的一个抽象。大概的工作原理也确实类似于Nginx,可以理解成在 Ingress 里建立一个个映射规则 , ingress Controller 通过监听 Ingress这个api对象里的配置规则并转化成 Nginx 的配置(kubernetes声明式API和控制循环) , 然后对外部提供服务。
搭建ingress–nginx控制器过程:
1.首先在nginx上下载对应镜像和yaml文件。 2.在三台设备上加载镜像。 3.应用其yaml文件生成ingress控制器:
[root@vms201 svc]
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
4.查看运行结果: 可以看到,控制器在vms202.rhce.cc node上运行。
[root@vms201 svc]
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NOD E READINESS GATES
ingress-nginx-admission-create-pc4pd 0/1 Completed 0 2m30s 10.244.185.75 vms203.rhce.cc <none> <none>
ingress-nginx-admission-patch-phkjj 0/1 Completed 1 2m30s 10.244.185.76 vms203.rhce.cc <none> <none>
ingress-nginx-controller-59b8bf5fdc-4x4x4 1/1 Running 0 2m30s 192.168.0.202 vms202.rhce.cc <none> <none>
5.修改外部设备的host文件,当访问web应用(2个pod)的域名时,都被解析为node2的ip 192.168.0.202。添加内容如下: 192.168.0.202 www1.rhce.cc 192.168.0.202 www2.rhce.cc
6.创建2个nginx的pod:
yaml文件为:
[root@vms201 svc]
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: web1
name: web1
spec:
terminationGracePeriodSeconds: 0
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: c1
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
创建两个nginx的pod:
[root@vms201 svc]
pod/web1 created
[root@vms201 svc]
pod/web2 created
[root@vms201 svc]
[root@vms201 svc]
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web1 1/1 Running 0 16s 10.244.185.69 vms203.rhce.cc <none> <none>
web2 1/1 Running 0 15s 10.244.185.70 vms203.rhce.cc <none> <none>
分别将其中主页修改为111和222,方便后续测试观察。
[root@vms201 svc]
[root@vms201 svc]
7.分别对于两个pod创建service
[root@vms201 svc]
service/svc1 exposed
[root@vms201 svc]
service/svc2 exposed
[root@vms201 svc]
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc1 ClusterIP 10.104.248.179 <none> 80/TCP 25s
svc2 ClusterIP 10.100.210.160 <none> 80/TCP 14s
8.设置ingress规则:
我们需要设置当访问www1.rhce.cc时,流量被发送到web1;当访问www2.rhce.cc时,流量被发送到web2 。注意:配置ingress规则不需要切换到ingress的命名空间。
编辑ingress规则的yaml文件(在官网查找格式:https://kubernetes.io/docs/concepts/services-networking/ingress/):
[root@vms201 svc]
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myingress
spec:
rules:
- host: www1.rhce.cc
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: svc1
port:
number: 80
- host: www2.rhce.cc
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: svc2
port:
number: 80
在测试环境中需要注释2句话。
9.创建ingress规则,应用到ingress上,然后测试:
[root@vms201 svc]
ingress.networking.k8s.io/myingress created
[root@vms201 svc]
NAME CLASS HOSTS ADDRESS PORTS AGE
myingress <none> www1.rhce.cc,www2.rhce.cc 10.108.154.34 80 29s
然后在修改了host文件的设备上访问www1.rhce.cc和www2.rhce.cc:测试成功。
注意点: 如果在别的命名空间里也创建了和当前命名空间一样的ingress规则,会报错,告知www1.rhce.cc这个域名已经被占用了。
参考资料: 6. 《老段CKA课程》 7. 链接:https://www.jianshu.com/p/fd597312751a 8. 链接:https://blog.csdn.net/yrx420909/article/details/105724292
|