问题描述
- 前端跨域问题:当页面中加载或者异步请求与原始页面不同源的资源时,浏览器的同源策略会拦截请求,比较简单的方式是后端增加一个 nginx 服务,给响应数据加上 CORS 相关的 header
- 证书问题:当 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 相关配置
- 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
复制代码
- 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