k8s 部署 HTTP3 Nginx
在之前的文章中介绍了如何编译支持HTTP3的Nginx 为了一键将H3 Nginx上云,一劳永逸,本文会记录一下容器化Nginx的过程。
制作镜像
Docker Image 的制作两种方法,支持 HTTP3 的 Nginx 在上面的文章中已经阐述(不过需要加上 nginx-upstream-dynamic-servers
这个模块,后面会用到),镜像的制作是将编译好的二进制文件打包放进 Docker 镜像当中,并保证其可运行。
方法 1:docker commit #保存 container 的当前状态到 image 后,然后生成对应的 image
方法 2:docker build #使用 Dockerfile 文件自动化制作 image
方法一: docker commit
获取 ubuntu 镜像
$ docker run -it ubuntu:latest /bin/bash
# 进入到容器后,创建 nginx 家目录
mkdir nginx
Nginx 的编译产物会全部放进 ${NGINX_QUIC_PATH}/objs 目录下。所以创建镜像需要将这里面的必要文件copy进镜像中。
|
|
因为Nginx依赖 Lua 动态库所以还需要将 luajit 等动态库拷贝进镜像中。并指明相关的动态库加载地址。
|
|
根据容器当前状态做一个 image 镜像
-m :提交时的说明文字; -a :提交的镜像作者;
|
|
方法二:DockerFile
使用 docker build 创建镜像时,需要使用 Dockerfile 文件自动化制作 image 镜像 Dockerfile 有点像源码编译时./configure 后产生的 Makefile
- 创建工作目录 和 DockerFile 文件
|
|
将 Nginx-http3 的编译产物和依赖动态库 copy 至该目录中
|
|
- 编写DockerFile
|
|
Dockerfile 的指令每执行一次都会在 docker 上新建一层,所以特别是 RUN 指令尽量通过 && 连在一起
关于Docker CMD 和 ENTRYPOINT ,可参考这篇文章, Dockerfile 语法参考 菜鸟,
关于为什么 CMD 要写成 daemon off
参考 Docker,为什么你容器刚启动就停了?
- 构建镜像文件
➜ docker build -t kiosk007/nginx-quic:v0.2 .
–tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签.
镜像打包好后,通过 docker images
命令可以看到当前的镜像id
- 运行容器
➜ docker run --name http3_nginx -v ~/Project/Nginx/nginx-quic:/nginx -p 8080:80 -p 8443:443/udp -p 8443:443/tcp -d 35edbb5f8be1
-p 8443:443 映射端口 -d 守护进程运行 -name http3_nginx 指定容器的名称 -v xxxx:/nginx 将宿主机的Nginx工作目录映射进容器中的工作目录
OK, 既然容器已经都起来了,那么访问一下吧,我本地的nginx conf 已经配置了 quic.kiosk007.top 的 quic 配置
,使用 quic 客户端访问一下吧。
|
|
提交镜像
登录到 https://hub.docker.com/
|
|
上传到 docker hub
|
|
在 docker hub 官网上可以找到自己上传上去的镜像。
大家如果想要获取我制作的 http3-nginx 镜像,可直接拉取
|
|
docker container prune # 删除所有停止的容器
docker image prune -a # 删除没有用到的镜像
kubernetes
minikube
Minikube是由Kubernetes社区维护的单机版的Kubernetes集群。并支持Kubernetes的大部分功能,从基础的容器编排管理,到高级特性如负载均衡、Ingress,权限控制等。
安装 minikube ,可参考官方文档 minikube start, 另外还需要安装 kubectl 另外做一些配置(如关掉 swap ),具体可参考我之前的文章 。
minikube 中安装 cni 是有版本依赖,老旧版本没有 –cni 选项,新版本可以支持
|
|
cni 插件安装:
flannel 服务在安装运行过程中依赖二进制文件 /opt/cni/bin/portmap ,没有会报错
安装 cni 后会在对应目录生成二进制文件
|
|
启动 minikube 集群
|
|
安装完成后, 查看状态 minikube status
|
|
- 使用minikube
|
|
- minikube 网络控制台
|
|
dashboard 是 kubernetes 的一个web UI,提供了友好的图形化界面,和对集群交互的基本操作功能。
Nginx
议题
- K8S集群中为什么需要负载均衡器
- 在K8S集群的边缘节点运行Nginx
- Nginx如何发现K8S中的服务
- K8S中的Ingress
基础概念介绍:
- K8S Deployments 介绍
为了实现在Kubernetes集群上部署容器化应用程序。需要创建一个Kubernetes Deployment,Deployment负责创建和更新应用。
典型的应用场景包括:
a. 定义Deployment来创建Pod和ReplicaSet b. 滚动升级和回滚应用 c. 扩容和缩容 d. 暂停和继续Deployment
参考:https://www.kubernetes.org.cn/deployment
- K8S Service 介绍
Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,也就是为一组Pod提供统一的访问方式。通常 Pod 通过 Label Selector 实现的与 Service 绑定。
一个 Service 在 Kubernetes 中是一个 REST 对象,和 Pod 类似。 像所有的 REST 对象一样, Service 定义可以基于 POST 方式,请求 apiserver 创建新的实例。 例如,假定有一组 Pod,它们对外暴露了 9376 端口,同时还被打上 “app=MyApp” 标签。
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
参考: https://kubernetes.io/zh/docs/concepts/services-networking/service/
如何获取K8S集群中的服务?
一般来说暴露服务的三种方式
NodePort: 将服务的类型设置成NodePort-每个集群节点都会在节点上打开一个端口,并将在该端口上接收到的流量重定向到基础服务。该服务仅在内部集群 IP 和端口上才可访间, 但也可通过所有节点上的专用端口访问。缺点是端口一般是奇怪的端口且端口肯定面临不够用的境地
LoadBalane: 将服务的类型设置成LoadBalance, NodePort类型的一 种扩展,这使得服务可以通过一个专用的负载均衡器来访问, 这是由Kubernetes中正在运行的云基础设施提供的。 负载均衡器将流量重定向到跨所有节点的节点端口。客户端通过负载均衡器的 IP 连接到服务。缺点是只是云厂商才能玩得起的(他们有路由器、有SDN)
Ingress: 创建一个Ingress资源, 这是一个完全不同的机制, 通过一个IP地址公开多个服务,就是一个网关入口。通过域名即可访问。推荐!!
虽然推荐 Ingress 的形式,但是也会先以 Headless Service 的方式搭建一个Nginx集群。
为何获取K8S中的服务是比较困难的?
- 每一个pod都有一个由网络层提供的私有地址,在K8S集群中的任一节点上可以可达。K8S集群外部不能直接访问。
- 一组相同功能的pod构成service,K8S赋予每个Service一个cluster IP地址。Service可以从cluster IP地址访问。
- Cluster IP地址只在K8S集群内有效,不能从外部直接访问。
外部应用如何才能访问K8S中的service?
- K8S集群中要有一个或者多个public IP边缘节点
- 外部要访问K8S集群内的Service必须通过边缘节点的public IP地址进行访问
- 有public IP地址的边缘节点需要部署如Nginx,HAProxy等反向代理将请求转发给K8S中的service
参考上图也就是边缘节点(Node)需要部署一台Nginx,Nginx有一个 public ip,用来访问k8s中的 service。那么Nginx的部署方式就有了2种。一种是部署在 K8S 集群内,一种是部署在K8S集群外。
2种方案有各自的优缺点。
Nginx 部署在K8S 外需要在Nginx的配置文件中指定nameserver;另外Nginx本身需要自己手动维护。
Nginx 部署在K8S 内则在Nginx的配置文件中不用指定nameserver,nameserver已由K8S配置好;K8S管理Nginx的启停;Nginx监听端口要映射到host的端口上或者使用host网络(hostNetwork:true)。
所以还是推荐使用部署在K8S内的方式。
通过NodePort发现服务
在k8s上可以给Service设置成NodePort类型,这样的话可以让Kubernetes在其所有节点上开放一个端口给外部访问(所有节点上都使用相同的端口号), 并将传入的连接转发给作为Service服务对象的pod。这样我们的pod就可以被外部请求访问到。至于服务都在哪些节点上,就需要动态更新Nginx的配置文件了(ngx_dynamic_upstream)
Nginx 配置如下
|
|
不过问题还是因为端口有限,暴露的服务也是有限的。
通过Headless service发现服务(推荐)
Service 是 Kubernetes 项目中用来将一组 Pod 暴露给外界访问的一种机制,如果不是 VIP 的方式访问,那么即是以 DNS 的方式访问,DNS访问又分两种,第一种,DNS的解析是VIP,那后面的访问形式和上述无异,另一种是DNS解析到POD上。
这就是 Headless Service,这里的区别在于,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。
这样的话Nginx需支持upstream动态后端服务器地址注册(nginx-upstream-dynamic-servers)
|
|
Nginx 可以部署在一个有外网ip(集群外部可访问ip)上。具体可以给Nginx的编排配置上加 Label Selector
当然,我们当前是Minikube单机模式,如果是正式的K8S环境,可以执行以下命令加标签
|
|
配置 minikube
minikube 和 标准的 k8s 还是有很多不一样。这里有很多坑其实我还没有踩明白。
搭建访问tunnel ip
|
|
挂载宿主机目录
minikube 需要额外执行这一步,才能将宿主机的目录挂载到Pod中的容器中
|
|
配置 k8s
可参考: ubuntu20.04 部署 Kubernetes (k8s)
Nginx depolyment
新建文件: nginx_http3-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-http3-deployment
namespace: default #必填,Pod所属的命名空间
labels: #自定义标签
app: nginx #自定义标签名字<key: value>
annotations: #自定义注释列表
imageregistry: "https://hub.docker.com/"
spec:
selector:
matchLabels:
app: nginx-http3 # 必填,通过此标签匹配对应pod
replicas: 1 # 副本集个数
template: # 必填,应用容器模版定义
metadata:
labels: # 必填,遇上面matchLabels的标签相同
app: nginx-http3
spec:
hostIPC: true
hostNetwork: true # 使用主机网络
#Pod的重启策略,Always表示一旦不管以何种方式终止运行,kubelet都将重启,OnFailure表示只有Pod以非0退出码退出才重启,Never表示不再重启该Pod
restartPolicy: Always
shareProcessNamespace: true # Pod 里的容器共享 PID Namespace
containers:
- name: nginx
image: weijiaxiang007/nginx-http3:v0.2
imagePullPolicy: IfNotPresent # [Always | Never | IfNotPresent] 获取镜像的策略 Alawys表示下载镜像;IfnotPresent表示优先使用本地镜像,否则下载镜像;Nerver表示仅使用本地镜像
workingDir: "/nginx/" # 选填,容器的工作目录
volumeMounts: # 挂载到容器内部的存储卷配置
- mountPath: "/nginx/"
name: nginx-vol # 引用pod定义的共享存储卷的名称
livenessProbe: # 对Pod内个容器健康检查的设置,当探测无响应几次后将自动重启该容器,检查方法有exec、httpGet和tcpSocket
httpGet:
path: /_check_domain_health
port: 80
scheme: HTTP
initialDelaySeconds: 30 # 容器启动完成后首次探测的时间,单位为秒
periodSeconds: 1200 # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
timeoutSeconds: 1 # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
resources:
requests:
cpu: 50m
memory: 200Mi
limits:
cpu: 500m
memory: 500Mi
env:
- name: NGINX_VERSION
value: "1.21.0"
- name: RUN_DEV
value: "boe"
- name: shell
image: ubuntu
stdin: true
tty: true
workingDir: "/nginx/" # 选填,容器的工作目录
command: ["/bin/bash","-c","sleep 100000000"]
volumeMounts: # 挂载到容器内部的存储卷配置
- mountPath: "/nginx/"
name: nginx-vol # 引用pod定义的共享存储卷的名称
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Nginx HTTP3 Server Start from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/sbin/nginx","-s","quit"]
volumes: # 在该pod上定义共享存储卷列表
- name: nginx-vol # 共享存储卷名称 (volumes类型有很多种)
hostPath:
path: /host
创建 deployment
|
|
查看 deployment 状态
|
|
验证
➜ curl https://webtransport.kiosk007.top:443/ping --resolve webtransport.kiosk007.top:443:192.168.49.2
pong