本文将介绍在 k8s 中向外界提供服务的几种方法port-forward
、NodePort
,以及 更加常用的提供服务的资源ingress
。本文要求对service
资源有一定的了解,如果你还不清楚的话,可以点击 k8s 中的服务如何沟通 简单学习一下。
接下来的内容需要一些pod
作为实际的服务处理者,你可以通过下面命令新建一个rs
,这个rs
会新建三个名为kubia-xxxx
的 pod,其8080
端口会开放一个服务,请求时会返回其所在节点名称:
kubectl create -f https://raw.githubusercontent.com/luksa/kubernetes-in-action/master/Chapter04/kubia-replicaset.yaml
port-forward 映射服务到端口
首先来看一下最简单粗暴的方法,我们可以通过一条 k8s 命令来将指定的 pod 端口映射到本地的端口上,基本用法如下:
kubectl port-forward <资源类型>/<资源名> <本机端口>:<资源端口>
例如,我有个 pod 叫做kubia
,它通过80
端口对外提供服务,那么我就可以使用kubectl port-forward pod/kubia 8080:80
将其映射到本机的8080
端口上。你可以使用kubectl port-forward -h
查看更多用法。
启用后会占用当前终端的标准输出,可以在后面添加&
来指定后台运行:
root@master1:~# k port-forward kubia-5k2zr 8080:8080 &
[1] 13949
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
root@master1:~# curl http://localhost:8080
Handling connection for 8080
You've hit kubia-5k2zr
port-forward
可以将 pod 临时映射出来,一般用于测试资源是否可用,在生产环境并不会大规模应用。
NodePort 映射服务到节点端口
相对于上一种port-forward
来说,这一种要正式的多,NodePort
可以将其 转发到所有 k8s 节点的指定端口上,并且不会像port-forward
一样在前台运行。我们先来新建一个NodePort
,新建nodeport.yaml
文件并填写如下内容,然后使用kubectl create -f nodeport.yaml
新建。:
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 30123
selector:
app: kubia
通过kind
属性可以看到NodePort
本质上是一个svc
资源,他通过指定spec.ports.nodePort
来讲服务映射到节点的端口上,接下来我们就可以访问下试试,直接curl
访问节点ip + 端口号
即可,你可以通过kubectl get nodes -o wide
找到所有 k8s 节点的 ip:
root@master1:~# curl http://192.168.56.11:30123
You've hit kubia-m68bq
root@master1:~# curl http://192.168.56.11:30123
You've hit kubia-flg8w
root@master1:~# curl http://192.168.56.11:30123
You've hit kubia-flg8w
root@master1:~# curl http://192.168.56.11:30123
You've hit kubia-8r2cg
root@master1:~# curl http://192.168.56.11:30123
You've hit kubia-m68bq
可以看到,使用了nodePort
之后,服务被正常的请求,并且也正常的被均衡到每一个 pod 上。
但是这里有个问题,假如我的 k8s 里运行了多个 web 应用服务器,我总不能让用户通过端口号http://domain:8081
、http://domain:8082
来访问不同的 web 服务吧。能不能处理成http://domain/web1
、http://domain/web2
...这种形式呢。
当然可以,这个就是接下来要说的Ingress
。
通过 Ingress 暴露服务
ingress 是啥?其实,ingress 就是一个nginx
服务器。从本质上说,我们可以直接通过配置nginx
服务器来实现刚才说的访问方式,但是这样每次svc
发生变更了我们就要重新手动配置一遍,好麻烦的,于是就有聪明的人想出来了,为什么我们不把复杂的配置操作抽象成一个文件,这样有新的变更的话我们直接修改文件,不就可以避免直接操作nginx
服务器了么?于是,ingress
诞生了。记住,ingress
本质上就是一个nginx
和许多个配置文件。
ingress 有两部分构成,负责转发服务的nginx-ingress-controller
和每个服务的配置文件ingress
资源,如下:
每个想要对外暴露的svc
都需要有一个对应的ingress
资源(配置文件)才能对外提供服务,ingress
资源并不负责实际的流量转发,它只是告诉nginx-ingress-controller
应该把流量转发到哪个svc
。
接下来,我们实践一下,从头部署一个可用的ingress
。
安装 ingress
k8s 并不自带 ingress,所以我们需要重新安装,幸好安装比较简单,直接执行如下命令即可,k8s 会根据其内容安装所有需要的资源。
kubectl apply -f https://raw.githubusercontent.com/StudyXX/google-containers/v1.10.2/install/ingress-nginx/ingress-nginx-controller.yaml
如果出现了镜像拉取失败的情况可以先手动将命令中的配置文件下载下来,然后将其的镜像地址从quay.io/
替换成quay-mirror.qiniu.com/
,重新kubectl apply
即可。文件地址为 github - StudyXX/ingress-nginx-controller.yaml 。
安装成功之后可以执行如下操作查看 ingress 是否安装完成,输入kubectl get namespaces
,查看ingress-nginx
(ingress相关的pod组) 的状态是否为Active
。输入kubectl get pod -n ingress-nginx
,查看nginx-ingress-controller
(负责实际转发流量的nginx) 的状态是否为Running
。
root@master1:~# kubectl get namespaces
NAME STATUS AGE
default Active 34d
ingress-nginx Active 27d
kube-node-lease Active 34d
kube-public Active 34d
kube-system Active 34d
root@master1:~# kubectl get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
default-http-backend-7vg6q 1/1 Running 1 26d
nginx-ingress-controller-5c4964c449-9ccbm 1/1 Running 2 26d
这两者正常基本可以断定ingress
安装成功了,接下来我们来创建一个对外服务。
使用 ingress 创建对外服务
在"通过 ingress 配置 service 访问"图里我们可以看出,使用 ingress 暴露服务需要先新建一个svc
,所以我们先使用如下内容创建一个,它会把 pod kubia
端口8080
上的服务转发到自己的8080
上,他控制的 pod 就是我们文章开头时新建的那些。
kubia-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 8080
targetPort: 8080
selector:
app: kubia
然后使用kubectl create -f kubia-svc.yaml
创建该svc
,接下来我们创建一个ingress
。ingress 支持创建HTTP
和HTTPS
的服务,接下来我们先来创建一个HTTP
的:
创建 HTTP 协议的访问
http
协议的访问比较简单,直接创建如下配置文件即可,通过这个ingress
配置文件,nginx-ingress-controller
就知道如何对外开发服务了。
kubia-http-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
# 将服务映射到该域名
- host: kubia.example.com
http:
paths:
# 通过 / 路径就可以访问该服务
- path: /
# 该服务后端 svc 的名称及端口号
backend:
serviceName: kubia
servicePort: 8080
然后使用kubectl create -f kubia-http-ingress.yaml
就可以创建ingress
了。我们可以使用kubectl describe ingress kubia
来查看他的介绍:
root@master1:~# k describe ingress kubia
Name: kubia
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
kubia.example.com / kubia:8080 (10.244.1.59:8080,10.244.2.31:8080,10.244.3.30:8080)
Annotations:
Events: <none>
ok,现在我们就可以通过http://kubia.example.com/
来访问目标svc
啦,这时你可能会疑惑,我没这个域名啊?没关系,在系统的hosts
文件里配置一下,将这个域名映射到nginx-ingress-controller
的 ip 地址就可以了,详情见下方:
配置域名到 ip 地址
首先获取
nginx-ingress-controller
的 ip 地址,执行如下命令后按Tab
补全名称,然后在IP
列就可以找到对应的 ip 了。kubectl describe pod -n ingress-nginx nginx-ingress-controller-
我的 ip 地址为
192.168.56.22
,执行vi /etc/hosts
,输入以下内容即可:192.168.56.22 kubia.example.com
修改完了之后,我们就可以进行访问了,执行curl http://kubia.example.com/
,就可以看到来自 svc kubia
的响应了。
root@master1:~# curl http://kubia.example.com/
You've hit kubia-m68bq
root@master1:~# curl http://kubia.example.com/
You've hit kubia-8r2cg
root@master1:~# curl http://kubia.example.com/
You've hit kubia-flg8w
创建 HTTPS 协议的访问
这里的 https 访问是指从客户端到 ingress 控制器之间的连接是加密的,而控制器与后端svc
及pod
之间的连接则还是 http,如下所示:
想要让ingress
可以提供https
服务,我们首先需要有证书和私钥,这里我们先来创建他们俩:
openssl genrsa -out tls.key 2048
openssl req -new -x509 \
-key tls.key \
-out tls.cert \
-days 360 \
-subj /CN=kubia.example.com
执行完之后你就可以在当前文件夹下发现两个文件,分别为tls.cert
和tls.key
。因为这种秘钥比较敏感,不适合直接挂载到 pod 上,所以可以使用 k8s 提供的专门用于提供敏感数据的资源secret
来存放它,我们先来新建一个名为tls-secret
的secret
资源:
kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
什么是
secret
资源?
secret
用于存放一些敏感的配置信息,如密码、密钥等,你可以把他理解成一个更安全的ConfigMap
资源,k8s 提供了很多类型的secret
,例如上文的kubectl create secret tls
就是一个常用的secret
。
然后我们就可以修改上文中的kubia-http-ingress.yaml
,将这个secret
挂载上去:
kubia-https-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
# 添加 tls 字段来启用 https
tls:
- hosts:
# 启用 https 的域名
- kubia.example.com
# 给其分配的证书及密钥,要求 tls 类型的 k8s secret 资源
secretName: tls-secret
rules:
- host: kubia.example.com
http:
paths:
- path: /
backend:
serviceName: kubia
servicePort: 8080
然后我们就可以通过以下命令来讲刚才创建的http
服务提升至https
,kubectl apply
依靠kind
及metadata
字段中的数据来寻找要修改的资源,所以改名字并不会造成什么影响:
kubectl apply -f kubia-https-ingress.yaml
然后输入curl -k -v https://kubia.example.com
,就可以发现我们的服务已经启用的https
连接:
root@master1:~# curl -k -v https://kubia.example.com
...
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
...
* Server certificate:
* subject: CN=kubia.example.com
...
You've hit kubia-flg8w
使用 ingress 暴露多个服务
在kubia-ingress.yaml
文件中可以看到,rules
和paths
都是数组,所以我们可以通过其暴露多个服务,如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: foo.example.com
http:
paths:
# 通过指定不同的路径来访问不同的服务
- path: /foo
backend:
serviceName: foo-svc
servicePort: 8080
- path: /bar
backend:
serviceName: bar-svc
servicePort: 8080
# 也可以通过指定多个 host 来配置不同的主机
- host: foo.example.com
http:
paths:
- path: /kubia
backend:
serviceName: kubia
servicePort: 8080
但是哪怕在一个文件中可以配置多个规则,但是依旧推荐为每一个svc
都创建一个自己专属的ingress
,这样条理会比较清晰,也方便日后的管理。
总结
本文介绍了如何将服务暴露出去,最简单的是port-forward
命令,他可以将指定资源的端口转发到本机,但是一般都用于测试。
其次是NodePort
模式,NodePort
本质上也是个svc
,只不过他在完成svc
本质工作的同时还将服务开放到了集群中所有节点的指定端口上。
但是用户更倾向于通过子路径访问服务,而不是端口。为了实现这个目标,我们可以使用ingress
资源,通过发布ingress
配置文件,nginx-ingress-controller
就可以将指定的服务发布到指定域名的指定路径上,ingress
支持发布HTTP
和HTTPS
连接。