vlambda博客
学习文章列表

容器化单页面应用中Nginx反向代理与Kubernetes部署

在《容器化单页面应用中RESTful API的访问》一文中,我介绍了一个在容器化环境中单页面应用访问后端服务的完整案例。这里我将继续使用这个案例,介绍一下容器化单页面应用部署的另一个场景:将Nginx的职责独立出来。

注:这里单页面应用是值一个包含前端页面、后端服务以及后台数据库的一个完整应用系统,这样符合微服务模式对于服务的定义。不过为了介绍简单,文章案例不使用后台数据库,而是将数据“写死”在后端服务中。

如果整个系统只有这一个单页面应用,那么这么做是简单且合理的;但如果一个系统包含多个单页面应用,或者说一个系统包含一个前端页面与多个后台服务,那么,将Nginx反向代理的职责加到这个前端页面的容器上,明显是不合理的。为什么不合理?因为一个系统有可能不仅仅有基于Web的UI,而且还有可能会有移动客户端,比如Andriod或者iOS的前端,甚至直接暴露API以供外部系统集成。如果运行前端页面的容器还兼职做反向代理的话,这些访问请求都将发送到前端单页面应用的服务器(容器)上,这样就会对前端应用造成压力。

因此,一个更好的做法是,将Nginx的反向代理职责从前端页面所运行的Nginx容器中独立出来。拓扑结构如下图所示:

我们将从以下几个方面对前文所述案例进行配置调整:

  • 简化前端应用的Nginx配置

  • Nginx反向代理容器的创建

  • 调整docker-compose.yml文件

简化前端应用的Nginx配置

在之前的案例中,前端应用的Nginx配置中还包含了反向代理的配置,这部分内容现在可以拿掉了,于是,前端应用的Nginx配置就非常简单了,只需要使用默认的静态页面服务配置即可,例如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

events {

    worker_connections 1024;

}

http {

    server {

      listen        80;

      server_name   localhost;

      include  /etc/nginx/mime.types;

      location / {

        root /usr/share/nginx/html;

        index  index.html  index.htm;

      }

    }

}

因此,在docker中完成前端页面的编译之后,将所有的资源复制到/usr/share/nginx/html下即可。前端Dockerfile如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

FROM nginx AS base

WORKDIR /app

EXPOSE 80

FROM node:10.16.0-alpine AS build

RUN npm install -g @angular/[email protected]

WORKDIR /src

COPY . .

RUN npm install

RUN ng build --prod --output-path /app

FROM base AS final

COPY --from=build /app /usr/share/nginx/html

COPY --from=build /src/nginx.conf /etc/nginx/nginx.conf

CMD ["nginx", "-g", "daemon off;"]

Nginx反向代理容器的创建

下一步就是创建一个Nginx反向代理的容器,基本思路是将反向代理配置到nginx.conf文件中,然后基于Nginx容器镜像,将nginx.conf文件复制到容器中即可。nginx.conf文件内容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

events {

    worker_connections 1024;

}

http {

    server {

      listen        80;

      server_name   localhost;

      include  /etc/nginx/mime.types;

      location / {

        root /usr/share/nginx/html;

        index  index.html  index.htm;

      }

      location /app {

      }

      location ~ ^/name-service/(.*)$ {

        rewrite ^ $request_uri;

        rewrite ^/name-service/(.*)$ $1 break;

        return 400;

      }

    }

    upstream namelistsvc {

      server namelist-service:5000;

    }

    upstream namelistcli {

      server namelist-client:80;

    }

}

上面定义了两个upstream,分别对应应用程序的前端和后端,然后根据不同的路径规则分别将请求路由到不同的服务器上。在Dockerfile中,只需要将该配置文件复制到Nginx的配置路径下即可:

1

2

3

FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf

CMD ["nginx", "-g", "daemon off;"]

调整docker-compose.yml文件

我们需要相应地调整docker-compose.yml文件,以便能够方便地将这些服务运行起来。docker-compose.yml文件非常简单:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

version: '3'

services:

  namelist-service:

    image: daxnet/namelist-service

  namelist-client:

    image: daxnet/namelist-client

  namelist-nginx:

    image: daxnet/namelist-nginx

    ports:

      - 80:80

    links:

      - namelist-service

      - namelist-client

对于namelist-service和namelist-client两个服务,我们没有指定TCP端口,因为这两个服务无需暴露出来,namelist-nginx服务会通过容器链接(links)由docker的DNS来解析这两个服务并在子网内部访问。

下面我们测试一下整个应用程序,使用下面的命令分别编译docker镜像,注意:编译前先进入client或service项目的根目录下:

1

2

$ docker build -t daxnet/namelist-client .

$ docker build -t daxnet/namelist-service .

然后,使用docker-compose up命令,启动所有服务,并使用浏览器访问Nginx反向代理服务的/app路径,得到如下结果:

容器化单页面应用中Nginx反向代理与Kubernetes部署

目前无需纠结上图中最后一个c415….是什么,它只不过是当前服务端机器的机器名称,在接下来Kubernetes部署阶段,我们会通过实验来验证namelist-service服务在Kubernetes中的伸缩性。

接下来,我们将name-list案例部署到Kubernetes上。在这里,我会使用Minikube来演示。Minikube是一套Kubernetes的最小集群,它只包含一个节点,但对于我们学习和实验来说已经够用。安装Minikube过程也不是特别容易,尤其是在国内的网络环境中,我推荐使用阿里云提供的相关资源以及使用Oracle Virtual Box来作为Minikube的虚拟化环境,这样安装过程最简单。我的Minikube是安装在Ubuntu 18.04的Linux机器上。

首先需要编写Kubernetes的部署描述文件,可以使用Kubernetes官方的Kompose工具,它能够帮助我们很方便地从docker-compose.yml文件生成Kubernetes的部署描述文件。对于name-list而言,我们已经有docker-compose.yml文件了,因此,使用Kompose工具一键生成即可:

1

$ kompose convert -o k8s.deployment.yaml

这条命令会将所有的部署脚本(包括deployment,service等)输出到同一个yaml文件中,如果不使用-o参数,那么就会分别输出到不同的文件中。但这都不是重点。重点是,我们还需要对生成的yaml文件进行一些修改。

第一个需要修改的地方是,要将namelist-nginx的service类型指定为NodePort,这样我们才可以使用Node IP来访问我们的应用程序。Minikube不支持LoadBalancer类型的service,因此,在访问应用程序之前,我们需要获取Node IP。在上文中我提到,namelist-service和namelist-client无需暴露端口出来,因为Nginx反向代理会将外部请求转发到这两个服务上。然而,由于没有暴露可访问的TCP端口,Kompose并不会对这两个服务产生service的定义,这就需要我们自己添加到所产生的k8s.deployment.yaml文件中,只不过我们不需要指定service的类型,因为我们不需要直接访问它们。

准备好部署文件之后,我们需要使用docker push命令,将namelist的三个docker镜像推送到Docker Hub上。Minikube默认会从Docker Hub上拉取镜像进行部署。这一步我就不多做说明了。

接下来,使用下面的命令将namelist应用部署到Kubernetes上:

1

$ kubectl apply -f k8s.deployment.yaml

部署完成后,查看deployments、services和pods:

容器化单页面应用中Nginx反向代理与Kubernetes部署

然后,使用kubectl cluster-info命令以获得Node IP:

容器化单页面应用中Nginx反向代理与Kubernetes部署

在浏览器中使用Node IP和Node Port来访问namelist应用程序:

容器化单页面应用中Nginx反向代理与Kubernetes部署

现在,将namelist-service扩展到2个实例:

容器化单页面应用中Nginx反向代理与Kubernetes部署

在浏览器中,反复刷新页面,可以看到,页面上显示的机器名在变化,证明Kubernetes将API访问请求重定向到不同的namelist-service服务实例:

容器化单页面应用中Nginx反向代理与Kubernetes部署

本文介绍了在namelist案例中,将Nginx反向代理职责从前端容器中独立出来的设计与实现,并介绍了Kubernetes部署的基本步骤和注意事项。基于namelist案例还可以继续扩展,比如使用HELM打包Kubernetes应用,今后有机会我会继续介绍。

本文源代码可以参考:https://github.com/daxnet/name-list/tree/k8s-deployment。

原文:https://sunnycoding.cn/2019/07/27/reverse-proxy-in-containerized-spa-and-kubernetes-deployment/