k8s nginx 跨域和 https 配置

5,496 阅读5分钟

问题描述

  1. 前端跨域问题:当页面中加载或者异步请求与原始页面不同源的资源时,浏览器的同源策略会拦截请求,比较简单的方式是后端增加一个 nginx 服务,给响应数据加上 CORS 相关的 header
  2. 证书问题:当 https 页面加载 http 资源或者没有有效证书的资源时,页面会出现 “不安全” 的标识,因此需要给请求资源的域名申请证书,可以使用 Let's Encrypt 免费申请证书。对于公司内部的域名等外网无法访问的情况,则只能自行购买证书,并在 nginx 中配置

下面配合 k8s 分别使用 nginx service 和 nginx ingress 两种方案实践

解决方案

方案一:使用 nginx service

分别部署两组服务,一个是 web app 的 service 和 deployment,另一个是 nginx,用户直接访问的是 nginx 的 service,再由 nginx 转发到 web app 所在的 service,这种过程与传统 nginx 的部署方式很相似,只需要将 proxy_pass 地址改为 k8s 内部 dns 可以解析的域名:my-svc.my-namespace.svc.cluster-domain.example,结构类似如下:

这种方案需要单独部署一个 nginx,并且需要将本地文件挂载到 pod 中去,需要根据 host 进行配置,并不能完全做到一键部署。

该方案文件目录如下:

app
├── nginx
   ├── deploy.yaml
   └── svc.yaml
├── app.conf      # nginx 配置文件
└── app.yaml

nginx 相关配置

  1. nginx service 和 deployment
# app/nginx/deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/default.conf  # nginx 配置文件在 pod 中的路径
          name: nginx-volume
        ports:
        - containerPort: 80
      volumes:
      - name: nginx-volume
        hostPath:
          path: /path/to/app/app.conf  # nginx 配置文件在本地的路径,将挂载到上面 volumeMounts 配置的路径,并覆盖 pod 中的 /etc/nginx/conf.d/default.conf

# app/nginx/svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    run: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: nginx
  1. nginx 配置
# app/app.conf
server {
	server_name _ ;
	listen 80;

	location / {  
		# 允许 CORS 所需要的 header,如果浏览器中仍然出现 Access-Control-Allow-Headers 相关的报错则需要另外添加和修改 Access-Control-Allow-Headers 这一项
	    add_header Access-Control-Allow-Origin *;
	    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
	    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

		proxy_pass http://app.default.svc.cluster.local:8018;  # 这里根据 k8s 集群内部的 dns 规则填写 web app 的地址,host 遵循 my-svc.my-namespace.svc.cluster-domain.example 的规则
	}
}

web app 相关配置

# app/app.yaml
apiVersion: v1
kind: Service
metadata:
  name: app
spec:
  selector:
    app: app
  ports:
  - protocol: "TCP"
    port: 8018
    targetPort: 8018
  # type: LoadBalancer 内网下无法使用 LoadBalancer 分配 External IP,会导致 service 的 External IP 一直是 <Pending>。因此不可使用 LoadBalancer 模式,可以使用默认的 NodePort
  
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  selector:
    matchLabels:
      app: app
  replicas: 2
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
      - name: app
        image: app:prod
        ports:
        - containerPort: 8018

HTTPS 证书配置

如果服务器拥有外网 IP 就可以使用 Let's Encrypt 等申请免费证书,一般流程是在服务器上使用 certbot 申请证书得到 .crt.key 文件,然后使用 k8s 的 volumes 将证书挂载到 nginx 容器的相应位置

# app/nginx/deploy.yaml
...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/default.conf  # nginx 配置文件在 pod 中的路径
          name: nginx-volume
        - mountPath: /etc/ssl/YOUR_SSL.crt  # crt 文件在 pod 中的路径
          name: ssl-crt
        - mountPath: /etc/ssl/YOUR_DOMAIN_NAME.key  # 域名的 key 文件在 pod 中的路径
          name: domain-key
        ports:
        - containerPort: 80
      volumes:
      - name: nginx-volume
        hostPath:
          path: /path/to/app/app.conf  # nginx 配置文件在本地的路径,将挂载到上面 volumeMounts 配置的路径,并覆盖 pod 中的 /etc/nginx/conf.d/default.conf
      - name: ssl-srt
	    hostPath:
	      path: /path/to/YOUR_SSL.crt  # crt 文件在本地的路径
	  - name: domain-key
	    hostPath:
	      path: /path/to/YOUR_DOMAIN_NAME.key  # 域名的 key 文件在本地的路径

nginx 相应配置如下:

server {  
	listen 443;  
  
	ssl on;  
	ssl_certificate /etc/ssl/YOUR_SSL.crt;  
	ssl_certificate_key /etc/ssl/YOUR_DOMAIN_NAME.key;  
  
	server_name YOUR.DOMAIN;  
	location / {  
	    proxy_pass http://app.default.svc.cluster.local:8018;
	}  
}

使用这种方法还需要在服务器上配置自动更新证书相关程序,同样可以通过 certbot 安装。

如果是自行购买的证书也可以按照上面的方式挂载和配置 .crt.key 文件

方案二

使用 Ingress,外部请求首先访问的是 Ingress,然后将请求分发到不同的 service,这样不必再另外创建一个 nginx 的 deployment ,结构如下:

该方案文件目录如下:

app
├── cloud-generic.yaml
├── mandatory.yaml
├── ingress.yaml
├── deploy.yaml
└── svc.yaml

配置 ingress-nginx

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml

上面 cloud-generic 中定义的 ingress-nginx 使用了 type: LoadBalancer,如果是内网环境,不能分配 External IP 则需要改成 type: NodePort

$ kubectl get pods -n ingress-nginx                                                                                      
NAME                                        READY   STATUS    RESTARTS   AGE                                                        nginx-ingress-controller-7dcc95dfbf-zxmc4   1/1     Running   0          11h
$ kubectl exec -it nginx-ingress-controller-7dcc95dfbf-zxmc4 bash -n ingress-nginx
www-data@nginx-ingress-controller-7dcc95dfbf-zxmc4:/etc/nginx$ cat nginx.conf
这里将输出 nginx 默认配置

下面配置 ingress,在 ingress 中设置跨域相关的 nginx 配置

# app/ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-connect-timeout: '30'
    nginx.ingress.kubernetes.io/proxy-send-timeout: '500'
    nginx.ingress.kubernetes.io/proxy-read-timeout: '500'
    nginx.ingress.kubernetes.io/send-timeout: "500"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-methods: "*"
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
    paths:
    - path: /app
      backend:
        serviceName: app
        servicePort: 8018

配制好 ingress.yaml 后再次查看 nginx-ingress pod 中 nginx 的配置如下:

# nginx.conf
## start server _
server {
    server_name _ ;

    listen 80 default_server reuseport backlog=511 ;
    listen 443 default_server reuseport backlog=511 ssl http2 ;
				
	...
				
    location /app{
        set $namespace      "default";
        set $ingress_name   "app-ingress";
        set $service_name   "";
        set $service_port   "";
        set $location_path  "/app";

        ...

        more_set_headers 'Access-Control-Allow-Origin: *';
        more_set_headers 'Access-Control-Allow-Credentials: true';
        more_set_headers 'Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS';
        more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
    }
    ...
}
## end server _

可以看到 CORS 相关的 header 已经被加入 /app 中,如果还需要添加其他的 location,则可以在 ingress.yaml 中的 spec/rules/paths 中配置

web app 的 deploy.yaml 和 svc.yaml 与前一种方法的相同

HTTPS 证书配置

对 ingress 配置 HTTPS 证书主要需要使用 cert manager,这个工具室 k8s 原生的证书管理工具,支持 Let's Encrypt 等免费证书,也支持自己购买的证书,同时提供了自动更新证书的功能,官方文档中已经给出了详细的使用方法,也可以参考 How to Set Up an Nginx Ingress with Cert-Manager on DigitalOcean Kubernetes

参考