eks-fargate服务最佳实践

一款客户端APP日志采集方案

Posted by 刘勇(lyonger) on 2021-05-08

eks-fargate服务最佳实践

需求来源

  • 项目在线上运行的时候,偶尔会遇到一些难缠的客户端问题。这些问题没有traceback,但是业务逻辑明显不对。我们很想收集客户端的log的信息来辅助我们查找问题,但是由于没有traceback,所以不会触发常规的错误和日志上传。因此我们想要做一个web服务。我们可以把要收集日志的客户端设备ID登记到这台服上,然后客户端启动时会访问这个web服务,如果发现自己需要提交历史日志,则打包将日志发送到appdump上。我们将这个web服务取名为controltower

  • 之所以使用一个额外的web服务来通知客户端上传日志,而不是通过服务端来通知,主要有两个原因:

    1
    2
    1. 我们需要登记哪些问题或者哪些设备需要上传日志,在一个web服务上进行登记操作比在服务端上做类似的操作更简单、更安全。
    2. 客户端可能在连接服务器之前就已经报错了,独立的web服务器在这种情况下仍然可以工作。
  • controltower的开发和部署有以下几个需求:

    1
    2
    3
    1. 开发效率高。显然controltower不是一个游戏的业务系统,这也意味着我们不可能花很多时间在这个web服务上。因此要求有很高的开发效率。
    2. 部署效率高。同上,我们也不可能花费很多精力在这个web服务的部署和维护上,所以越简单越好。
    3. 自动扩缩容。这个web服务通常只会在客户端启动的时候访问一次。QPS比较低,但是对于维护后开服的这种特殊时段,或者某个活动开启的时段,可能有集中的大量访问,因此我们希望这个web服务能够自动扩缩容。

技术选型

  • 在最开始的时候我们考虑的是FaaS来实现。理论上只用写function,部署也非常简单。由于项目目前是在海外运营,所以我们首先考虑了AWS lambda。但是考虑到未来项目也会在国内运营,我们希望相同的服务在国内也能部署,因此最终放弃了AWS lambda

  • 去掉lambda选项之后,docker部署成为了下一个选项。这也是我们最终选择的方案。AWS的EKS+fargate。具体的EKS+fargate方案将在后面介绍。

  • 在Web框架的选择上,由于项目内的语言栈主要以python为主,因此我们在python web框架中进行了选择。考虑到controltower是一个API web服务,而非html web服务,因此我们选择了最近风头正盛的fastapi框架。主要看重的是:

    1
    2
    1. 原生异步提供的高性能
    2. 通过type hint原生支持swagger文档,方便调试
  • 数据库上我们使用了mongodb,这是公司内部使用比较多的数据库,无论是开发还是运维同学都比较熟悉。在fastapi内使用基于asynciomotor做为mongodb client

  • 在部署上,我们使用了uvicorn-gunicorn-fastapi-docker 做为基础镜像。这个镜像内部的主要结构为:

    1
    2
    3
    1. 前端使用Gunicorn接收http请求,根据pod的CPU核心数量生成对应的worker进程,默认1:1,即一个CPU核心一个worker进程
    2. Worker进程内使用[uvicorn](http://undefined/) 做为ASGI server,这个也是fastapi推荐使用的ASGI server
    3. 每个uvicorn进程里运行一个fastapi应用
  • 举例来说,如果我们启动一个4核心的pod,则该pod内会启动4个uvicorn进程,和mongodb建立4个连接。由于所有的状态都在mongodb里,所以服务本身是可以轻松水平扩展的,只要最终不超过mongodb的承载能力即可。

EKS的特性说明

托管方式

  • EKS一共有三种托管方式,产品详情可查看官网
    • EKS on AWS EC2。拉起一堆EC2作为node,用户得自己维护EC2(也包括SOPT实例)。
    • EKS on 自己的机器。支持把自己准备的机器 使用eks来做调度。
    • EKS on AWS Fargate。控制面和NODE均托管,serverless特性的KaaS产品。

收费方式

  • 控制面不收费,一个pod一个node,只按pod的资源收费。

扩缩容方式

  • EKS fargate并不支持node级别的纵向扩容,只支持pod级别的纵向扩容,pod级别的纵向扩容的方式,都需要在pod重启时完成(分为手动重启和自动重启)。
  • EKS本身提供横向的auto scalling,当一个pod支持不住的时候会拉起一个新的pod。

部署流程

准备环境

控制机器

  • 创建一台EC2机器用作控制机,如果接入了gitlab-runner,其实可以省去控制机的安装,控制机器上安装的工具如下:
1
2
3
4
5
6
7
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
aws --version
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl
mv kubectl /usr/local/bin/
chmod +x /usr/local/bin/kubectl

配置IAM

配置service-account

  • 配置aws-load-balancer-controller组件所需的service-account,在控制机器上kubectl apply
1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: aws-load-balancer-controller
name: aws-load-balancer-controller
namespace: kube-system
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::478622463896:role/AmazonEKSLoadBalancerConrollerRole

部署流程

镜像认证

  • 创建镜像认证凭据。
1
kubectl create secret docker-registry ${IMAGE_PULL_SECRETS} --docker-server=${仓库地址} --docker-username=${仓库用户名} --docker-password=${仓库密码} --namespace=${NS名称}

部署LB控制器

  • 参考这里部署 部署aws-load-balancer-controller,注意需安装在kube-system这个namespace下。

部署服务端

  • 创建ns,为了适配gitlab的cicd发布,ns的名称需符合规范: {project_name}-{project_id},比如:
1
kubectl create ns mini-hunter-9391
  • 部署min-hunter-server服务,其deployment的yaml如下:
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
apiVersion: apps/v1
kind: Deployment
metadata:
name: mini-hunter-server
namespace: ${KUBE_NAMESPACE}
spec:
selector:
matchLabels:
app: mini-hunter-app
replicas: 1
template:
metadata:
labels:
app: mini-hunter-app
spec:
containers:
- name: mini-hunter-container
imagePullPolicy: IfNotPresent
image: ${DOCKER_IMAGE}:${IMAGE_TAG}
env:
- name: HUNTERMINI_WEB_HOST
value: ${HUNTERMINI_WEB_HOST}
- name: HUNTERMINI_WEB_PORT
value: "8000"
ports:
- containerPort: 8000
resources:
limits:
cpu: 1000m
memory: 6144Mi
requests:
cpu: 500m
memory: 4096Mi
imagePullSecrets:
- name: ${IMAGE_PULL_SECRETS}
  • 通过Service和Ingress暴露7层应用服务。
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
apiVersion: v1
kind: Service
metadata:
namespace: ${KUBE_NAMESPACE}
name: service-8000-${KUBE_NAMESPACE}
spec:
ports:
- port: 80
targetPort: 8000
protocol: TCP
type: NodePort
selector:
app: mini-hunter-app
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
namespace: ${KUBE_NAMESPACE}
name: ingress-8000-${KUBE_NAMESPACE}
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=600
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 8000}]'
alb.ingress.kubernetes.io/tags: Project=${PROJECT},Cost Project=${COST_PROJECT}
alb.ingress.kubernetes.io/security-groups: ${SECURITY_GROUPS}
spec:
rules:
- http:
paths:
- path: /*
backend:
serviceName: service-8000-${KUBE_NAMESPACE}
servicePort: 80
  • 通过创建service资源暴露4层应用服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
namespace: ${KUBE_NAMESPACE}
name: service-20000-${KUBE_NAMESPACE}
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb-ip
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: Project=${PROJECT}, Cost Project=${PROJECT}
spec:
ports:
- port: 20000
targetPort: 20000
protocol: TCP
type: LoadBalancer
selector:
app: mini-hunter-app

部署web控制台

  • 部署min-hunter-web服务,其deployment的yaml如下:
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
38
39
40
41
42
43
44
45
46
47
48
 apiVersion: apps/v1
kind: Deployment
metadata:
name: controltower
namespace: ${KUBE_NAMESPACE}
spec:
selector:
matchLabels:
app: controltower
replicas: 1
template:
metadata:
labels:
app: controltower
spec:
containers:
- name: controltower-container
imagePullPolicy: IfNotPresent
image: ${DOCKER_IMAGE}:${IMAGE_TAG}
ports:
- containerPort: 80
resources:
limits:
cpu: 1000m
requests:
cpu: 500m
env:
- name: MONGODB_URL
value: mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOST}/controltower
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["/bin/bash", "-c", "touch /tmp/prestop && sleep 60"]
terminationGracePeriodSeconds: 130
imagePullSecrets:
- name: ${IMAGE_PULL_SECRETS}
  • 通过Service和Ingress暴露7层应用服务。
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
38
39
40
41
42
43
44
45
46
47
48
49
50
apiVersion: v1
kind: Service
metadata:
namespace: ${KUBE_NAMESPACE}
name: service-${KUBE_NAMESPACE}
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app: controltower
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
namespace: ${KUBE_NAMESPACE}
name: ingress-${KUBE_NAMESPACE}
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/backend-protocal: HTTP
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
alb.ingress.kubernetes.io/certificate-arn: ${ACM_CERT}
alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=600
alb.ingress.kubernetes.io/tags: Project=${PROJECT},Cost Project=${COST_PROJECT}
alb.ingress.kubernetes.io/conditions.rule-path-doc: >
${URL_FORWARD_RULES}
alb.ingress.kubernetes.io/actions.rule-path-doc: >
${URL_FORWARD_ACTIONS}
alb.ingress.kubernetes.io/actions.response-403: >
{"Type":"fixed-response","FixedResponseConfig":{"ContentType":"text/plain","StatusCode":"403"}}
spec:
rules:
- http:
paths:
- path: /docs
backend:
serviceName: rule-path-doc
servicePort: use-annotation
- path: /docs
backend:
serviceName: response-403
servicePort: use-annotation
- path: /*
backend:
serviceName: service-${KUBE_NAMESPACE}
servicePort: 80

扩缩容服务

  • 使用horizontal-pod-autoscaler组件对web平台进行水平的自动扩缩容,通过修改deploymentreplica值来自动扩缩容pod个数。

部署Metrics Server

  • 需要部署Metrics Server,用于采集pod的负载数据。
1
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.7/components.yaml
  • 通过如下命令查看是否成功。
1
kubectl get deployment metrics-server -n kube-system

部署HPA

  • 设置web服务的hpa服务。
1
kubectl autoscale deployment mini-hunter-web --cpu-percent=50 --min=1 --max=10 --namespace=mini-hunter-9391

Gitlab的CICD发布

  • EKS默认可以集成Gitlab的CICD发布。关于如何集成EKS到Gitlab请参考这篇文章,这里主要介绍集成之后如何配置CICD。

  • 由于Gitlab集成EKS后,会注入环境变量到gitlab的runner,所以runner可以通过kubectl命令控制EKS集群。

  • controltowerpipeline的流水线效果如下,可自助在Gitlab推送代码后进行CICD发布。
    image

  • mini-hunterpipeline的流水线效果如下,可自助在Gitlab推送代码后进行CICD发布。

    image



支付宝打赏 微信打赏

赞赏一下