在Kubernetes中,服务和Pod的IP地址仅可以在集群网络内部使用,对于集群外的应用是不可见的。为了使外部的应用能够访问集群内的服务,在Kubernetes中目前提供了以下几种方案:

  1. NodePort
  2. LoadBalancer
  3. Ingress

NodePort,简单来说,就是通过service这种资源对象,为后端pod提供一个统一的访问接口,然后将service的统一访问接口映射到群集节点上,最终实现client通过映射到群集节点上的端口访问到后端pod提供的服务。

但是,这种方式有一个弊端,就是当新生成一个pod服务就需要创建对应的service将其映射到节点端口,当运行的pod过多时,我们节点暴露给client端的端口也会随之增加,这样我们整个k8s群集的危险系数就会增加,因为我们在搭建群集之处,官方明确指出,必须关闭firewalld防火墙及清空iptables规则,现在我们又暴露了那么多端口给client,安全系数可想而知。

有没有更安全又简便的一种方法呢?答案是肯定的,就是来利用Ingress这种资源对象来实现。

一、lngress-nginx

1、lngress-nginx组成

  • ingress-nginx-controller:根据用户编写的ingress规则(创建的ingress的yaml文件),动态的去更改nginx服务的配置文件,并且reload重载使其生效(是自动化的,通过lua脚本来实现);
  • ingress资源对象:将Nginx的配置抽象成一个Ingress对象,每添加一个新的Service资源对象只需写一个新的Ingress规则的yaml文件即可(或修改已存在的ingress规则的yaml文件)

2、lngress-nginx可解决什么问题呢?

1、动态配置服务   如果按照传统方式, 当新增加一个服务时, 我们可能需要在流量入口加一个反向代理指向我们新的k8s服务. 而如果用了Ingress-nginx, 只需要配置好这个服务, 当服务启动时, 会自动注册到Ingress的中, 不需要而外的操作。 2、减少不必要的端口映射   配置过k8s的都清楚, 第一步是要关闭防火墙的, 主要原因是k8s的很多服务会以NodePort方式映射出去, 这样就相当于给宿主机打了很多孔, 既不安全也不优雅. 而Ingress可以避免这个问题, 除了Ingress自身服务可能需要映射出去, 其他服务都不要用NodePort方式

3、lngress-nginx工作原理

1、ingress controller通过和kubernetes api交互,动态的去感知集群中ingress规则变化; 2、然后读取它,按照自定义的规则,规则就是写明了哪个域名对应哪个service,生成一段nginx配置; 3、再写到nginx-ingress-controller的pod里,这个Ingress controller的pod里运行着一个Nginx服务,控制器会把生成的nginx配置写入/etc/nginx.conf文件中; 4、然后reload一下使配置生效。以此达到域名分别配置和动态更新的问题;

二、Ingress-nginx配置示例

注:

1、搭建registry私有库(可忽略配置私有库,手动导入所需镜像到相应节点)

//运行registry私有库
[root@docker-k8s01 ~]# docker run -itd --name registry -p 5000:5000 --restart always registry
//编辑配置文件,指定私有仓库
[root@docker-k8s01 ~]# vim /usr/lib/systemd/system/docker.service 
ExecStart=/usr/bin/dockerd -H unix:// --insecure-registry 192.168.171.151:5000
[root@docker-k8s01 ~]# vim /usr/lib/systemd/system/docker.service 
//将配置好的文件发放到其他两台节点
[root@docker-k8s01 ~]# scp /usr/lib/systemd/system/docker.service docker-k8s02:/usr/lib/systemd/system/ 
[root@docker-k8s01 ~]# scp /usr/lib/systemd/system/docker.service docker-k8s03:/usr/lib/systemd/system/
//在各个节点执行如下命令,重启docker使配置生效
[root@docker-k8s01 ~]# systemctl daemon-reload 
[root@docker-k8s01 ~]# systemctl restart docker 
[root@docker-k8s01 ~]# docker push 192.168.171.151:5000/httpd:v1 
[root@docker-k8s01 ~]# docker push 192.168.171.151:5000/tomcat:v1 

2、创建namespace(可忽略,使用默认的default名称空间也可以,但需要删除下面所有yaml文件中关于自定义的名称空间的配置字段)

//创建名称空间test-ns
[root@docker-k8s01 ~]# kubectl create ns test-ns
//确定创建成功
[root@docker-k8s01 ~]# kubectl get ns
NAME              STATUS   AGE
test-ns           Active   6s

3、创建Deployment、Service资源对象

//创建httpd服务及service

---
kind: Deployment
apiVersion: extensions/v1beta1
metadata: 
  name: web01
  namespace: test-ns
spec: 
  replicas: 3
  template: 
    metadata: 
      labels: 
        app: httpd01
    spec: 
      containers: 
      - name: httpd
        image: 192.168.171.151:5000/httpd:v1
---
apiVersion: v1
kind: Service
metadata:
  name: httpd-svc
  namespace: test-ns
spec:
  selector:
    app: httpd01
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
//执行yaml文件
[root@docker-k8s01 test]# kubectl apply -f httpd-01.yaml 

//创建tomcat服务及service

---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: web02
  namespace: test-ns
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: tomcat01
    spec:
      containers:
      - name: tomcat
        image: 192.168.171.151:5000/tomcat:v1
---
apiVersion: v1
kind: Service
metadata:
  name: tomcat-svc
  namespace: test-ns
spec:
  selector:
    app: tomcat01
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080

//执行yaml文件
[root@docker-k8s01 test]# kubectl apply -f tomcat-01.yaml 

//确定成功创建上述资源对象

//确定pod是正常运行状态
[root@docker-k8s01 test]# kubectl get po -n test-ns 
NAME                     READY   STATUS    RESTARTS   AGE
web01-6849f7dd96-cglhg   1/1     Running   0          8m25s
web01-6849f7dd96-n57kx   1/1     Running   0          8m25s
web01-6849f7dd96-r6m26   1/1     Running   0          8m25s
web02-65b5798c6-8hzf9    1/1     Running   0          77s
web02-65b5798c6-r7wrl    1/1     Running   0          77s
web02-65b5798c6-zxx8h    1/1     Running   0          77s
//确定SVC已经成功创建
[root@docker-k8s01 test]# kubectl get svc -n test-ns 
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
httpd-svc    ClusterIP   10.107.178.33    <none>        80/TCP     8m8s
tomcat-svc   ClusterIP   10.110.129.210   <none>        8080/TCP   94s
//访问SVC的clusterIP+端口,确定可以访问到后端Pod
[root@docker-k8s01 test]# curl -I 10.107.178.33:80     //访问httpd
HTTP/1.1 200 OK
Date: Sat, 12 Sep 2020 03:35:27 GMT
Server: Apache/2.4.46 (Unix)       //版本号也可看到
Last-Modified: Sat, 12 Sep 2020 03:05:22 GMT
ETag: "11-5af15126f2080"
Accept-Ranges: bytes
Content-Length: 17
Content-Type: text/html

[root@docker-k8s01 test]# curl -I 10.105.121.123:8080    //访问tomcat
HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 12 Sep 2020 04:04:22 GMT
#如果在上述访问测试中,没有访问到相应的pod,建议使用“kubectl describe svc”命令,
#查看相应的service中的Endpoints列中有没有关联后端pod。

4、创建Ingress-nginx资源对象

**方法1:**去gitlab搜索Ingress-nginx,点击“deploy”,再点击页面下的跳转链接,即可看到如下命令:

//这里先不要直接复制命令到终端先将yaml下载下来
[root@docker-k8s01 test]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
//编辑其yaml文件
[root@docker-k8s01 test]# vim mandatory.yaml 

    spec:          //这是212行的spec字段
      hostNetwork: true            //添加此行,表示使用主机网络
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        Ingress: nginx            //设置节点的标签选择器,指定在哪台节点上运行
      containers:
        - name: nginx-ingress-controller
          image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.29.0            //这里我替换为了阿里云的镜像
//上面是指定使用什么镜像,若需要更改镜像名称,改这里即可
//对docker-k8s02打上相应的标签,以便指定ingress-nginx运行在docker-k8s02上
[root@docker-k8s01 test]# kubectl label nodes docker-k8s02 Ingress=nginx
//执行如下命令查看标签是否存在
[root@docker-k8s01 test]# kubectl get nodes docker-k8s02 --show-labels 
//切换到k8s02节点上执行如下命令,手动导入Ingress-nginx镜像(自行上传)
[root@docker-k8s02 ~]# docker load < nginx-ingress-controller-0.29.0.tar
//回到k8s01节点执行yaml文件
[root@docker-k8s01 test]# kubectl apply -f mandatory.yaml 

关于上面yaml文件中写入的“hostNetwork: true”具体解释:如果使用此网络参数,那么pod中运行的应用程序可以直接使用Node节点端口,这样node节点主机所在的网络的其他主机,都可以通过访问到此应用程序。

确定Ingress-nginx的容器运行正常

[root@docker-k8s01 test]# kubectl get pod -n ingress-nginx -o wide
NAME                                        READY   STATUS             RESTARTS   AGE   IP                NODE           NOMINATED NODE   READINESS GATES
nginx-ingress-controller-845fbb7b4b-5gtkc   0/1     ImagePullBackOff   0          70s   192.168.171.150   docker-k8s02   <none>           <none>

5、定义Ingress规则(编写ingress的yaml文件)

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  namespace: test-ns
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: www.test01.com
    http:
      paths:
      - path: /
        backend:
          serviceName: httpd-svc
          servicePort: 80
      - path: /tomcat
        backend:
          serviceName: tomcat-svc
          servicePort: 8080
//执行ingress规则的yaml文件
[root@docker-k8s01 test]# kubectl apply -f ingress.yaml 
//查看ingress规则资源对象
[root@docker-k8s01 test]# kubectl get ingresses -n test-ns 
NAME           HOSTS            ADDRESS   PORTS   AGE
test-ingress   www.test01.com             80      14s

其实,至此已经实现了我们想要的功能,现在就可以通过www.test01.com 来访问到我们后端httpd容器提供的服务,通过www.test01.com/tomcat 来访问我们后端tomcat提供的服务,当然,前提是自行配置DNS解析,或者直接修改client的hosts文件。访问页面如下(注意:一定要自己解决域名解析的问题,若不知道域名对应的是哪个IP,请跳过这两个图,看下面的文字解释):

//访问httpd服务

//访问Tomcat

在上面的访问测试中,虽然访问到了对应的服务,但是有一个弊端,就是在做DNS解析的时候,只能指定Ingress-nginx容器所在的节点IP。而指定k8s集群内部的其他节点IP(包括master)都是不可以访问到的,如果这个节点一旦宕机,Ingress-nginx容器被转移到其他节点上运行(不考虑节点标签的问题,其实保持Ingress-nginx的yaml文件中默认的标签的话,那么每个节点都是有那个标签的)。随之还要我们手动去更改DNS解析的IP(要更改为Ingress-nginx容器所在节点的IP,通过命令“kubectl get pod -n ingress-nginx -o wide”可以查看到其所在节点),很是麻烦。

有没有更简单的一种方法呢?答案是肯定的,就是我们为Ingress-nginx规则再创建一个类型为nodePort的Service,这样,在配置DNS解析时,就可以使用www.test01.com 绑定所有node节点,包括master节点的IP了,很是灵活。

6、为Ingress规则创建一个service

刚才找到Ingress-nginx的yaml文件的页面,然后下拉页面,即可看到以下,可以根据k8s集群环境来选择适合自己的yaml文件,假如自己是在Azure云平台搭建的K8s集群,则选择复制Azure下面的命令即可,我这里是自己的测试环境,所以选择Bare-metal下面的yaml文件:

创建这个Service有两种方法,一是直接复制其web页面的命令到master节点上执行,二是将其链接地址复制到终端使用wget下载下来再执行,我选择第二种方法,因为我想看看里面的内容:

[root@docker-k8s01 test]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/service-nodeport.yaml
//查看内容,不需要修改
[root@docker-k8s01 test]# cat service-nodeport.yaml 
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
//执行yaml文件
[root@docker-k8s01 test]# kubectl apply -f service-nodeport.yaml 
service/ingress-nginx created
//查看运行的service
[root@docker-k8s01 test]# kubectl get svc -n ingress-nginx 
NAME            TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   10.104.185.116   <none>        80:30483/TCP,443:32362/TCP   8s
//可以看到service分别将80和443端口映射到了节点的30483和32362端口(随机映射的,也可以修改yaml文件指定端口)

至此,域名解析对应的IP可以是k8s群集内的任意节点IP

基于虚拟主机的Ingress规则

如果现在是另一种需求,我需要将www.test01.com 和www.test02.com 都对应上我后端的httpd容器提供的服务,那么此时应该怎么配置?

//修改ingress规则的yaml文件如下
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  namespace: test-ns
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: www.test02.com       //增加这一段host配置
    http:
      paths:
      - path: /
        backend:
          serviceName: httpd-svc       //绑定和www.test01相同的service名称
          servicePort: 80
  - host: www.test01.com
    http:
      paths:
      - path: /
        backend:
          serviceName: httpd-svc
          servicePort: 80
      - path: /tomcat
        backend:
          serviceName: tomcat-svc
          servicePort: 8080
//重新执行yaml文件
[root@docker-k8s01 test]# kubectl apply -f ingress.yaml 

访问www.test01.com:30483

访问www.test02.com:30483

上述示例的pod是如何一步一步可以使client访问到的呢?

后端Pod---->service---->ingress规则---->写入ingress-nginx-controller配置文件并自动重载使之生效---->对ingress-nginx创建service---->实现client无论通过哪个k8节点的IP+端口都可以访问到后端pod

三、配置HTTPS

在上面的操作中,实现了使用ingress-nginx为后端所有pod提供一个统一的入口,那么,有一个非常严肃的问题需要考虑,就是如何为我们的pod配置CA证书来实现HTTPS访问?在pod中直接配置CA么?那需要进行多少重复性的操作?而且,pod是随时可能被kubelet杀死再创建的。当然这些问题有很多解决方法,比如直接将CA配置到镜像中,但是这样又需要很多个CA证书。

这里有更简便的一种方法,就拿上面的情况来说,后端有多个pod,pod与service进行关联,service又被ingress规则发现并动态写入到ingress-nginx-controller容器中,然后又为ingress-nginx-controller创建了一个Service映射到群集节点上的端口,来供client来访问。

在上面的一系列流程中,关键的点就在于ingress规则,我们只需要在ingress的yaml文件中,为域名配置CA证书即可,只要可以通过HTTPS访问到域名,至于这个域名是怎么关联到后端提供服务的pod,这就是属于k8s群集内部的通信了,即便是使用http来通信,也无伤大雅。

//配置如下

接下来的配置与上面的配置基本没什么关系,但是由于上面已经运行了Ingress-nginx-controller容器,所以这里就没有必要再运行了。只需要配置pod、service、ingress规则即可。

//创建CA证书(测试环境可自行创建)
[root@docker-k8s01 https]# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
//查看生成的两个文件
[root@docker-k8s01 https]# ls
tls.crt  tls.key
//将生成的CA证书存储到etcd
[root@docker-k8s01 https]# kubectl create secret tls tls-secret --key=tls.key --cert tls.crt
//创建deploy、service、ingress资源对象
//编写yaml配置文件
kind: Deployment
apiVersion: extensions/v1beta1
metadata: 
  name: web03
spec: 
  replicas: 2
  template: 
    metadata: 
      labels: 
        app: httpd03
    spec: 
      containers: 
      - name: httpd3
        image: 192.168.171.151:5000/httpd:v1
---
apiVersion: v1
kind: Service
metadata: 
  name: httpd-svc3
spec: 
  selector: 
    app: httpd03
  ports: 
  - protocol: TCP
    port: 80
    targetPort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata: 
  name: test-ingress3
spec: 
  tls: 
    - hosts: 
      - www.test03.com
      secretName: tls-secret
  rules: 
  - host: www.test03.com
    http: 
      paths:
      - path: /
        backend: 
          serviceName: httpd-svc3
          servicePort: 80
//执行yaml文件
[root@docker-k8s01 https]# kubectl apply -f httpd02.yaml 

//确认创建的资源对象正常运行

//查看svc
[root@docker-k8s01 https]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
httpd-svc3   ClusterIP   10.110.199.104   <none>        80/TCP    5m35s
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP   26d
//查看pod
[root@docker-k8s01 https]# kubectl get pod 
NAME                    READY   STATUS    RESTARTS   AGE
web03-c496b69f5-7288z   1/1     Running   0          6m23s
web03-c496b69f5-hnmrg   1/1     Running   0          6m23s
//查看ingress规则
[root@docker-k8s01 https]# kubectl describe ingresses 
Name:             test-ingress3
Namespace:        default
Address:          10.104.185.116
Default backend:  default-http-backend:80 (<none>)
TLS:
  tls-secret terminates www.test03.com
Rules:
  Host            Path  Backends
  ----            ----  --------
  www.test03.com  
                  /   httpd-svc3:80 (10.244.1.8:80,10.244.2.8:80)
//确定关联到对应的service及后端pod

//https访问测试

访问https://www.test03.com