使用k8s和API Gateway(Ambassador)进行容器管理
你好。我是kwashi。今天我介绍一下我使用SPA前端和API服务器的组合构建的系统。之前的工作中,由于在单体系统中添加大规模功能而遇到了困难,因此我在实施过程中注重功能和结构的分离。如果你想参考Ambassador或k8s的清单,请看文章的后半部分。
※ 我们开发的系统并不是为了提供某种服务规范,而是为了进行技术验证和组合作为投资组合的目的而创建的。
SPA是为了避免在服务器端进行HTML渲染,而将服务器端的角色完全委托给API服务器而使用的。每个API都通过容器化来进行分离,通过容器管理工具来管理各个服务,以便实现易于扩展和部署。此外,我们还使用API网关来路由流向各个API的流量。
如果按照上述结构来设计,由于在API服务器和前端两者都需要安全地实施认证功能,这样很麻烦。 因此,我们将使用名为Firebase Authentication的认证基础设施来进行认证。
由于在技术实施方面存在许多与其他文章重复的内容,因此本文准备介绍另一篇Qiita文章,包括相关参考资料。然后,本文将描述系统概述、k8s管理技巧以及API Gateway大使的配置文件。
在这次的实现中,我们将与数据库访问相关的部分进行了隔离,并且省略了与数据库相关的实现。
git:(k-washi/example-k8s-ambassador)[https://github.com/k-washi/example-k8s-ambassador] 的 GitHub 链接。
系统概述 (xì shù)
按照图示,客户端使用Vue,并由Nginx调用。API服务器由k8s管理,其中包含不需要认证的REST-API1和需要JWT验证的REST-API2,以及用于JWT验证的JWT-AutoO DockerImage。
此外,还使用Ambassador作为API网关来进行路由控制。
在客户端上,与Firebase进行整合,实现了登录、用户注册、获取和更新用户信息的功能。
此外,在REST-API1中,提供了通过简单的JSON进行GET和POST的功能。
REST-API2作为基于用户信息的服务提供的示例,提供了涉及JWT依赖的身份验证和授权的用户信息。REST-API2的身份验证和授权通过gRPC将JWT发送给JWT-AuthO,并在JWT-AuthO与Firebase进行整合后进行JWT的验证。
同时,将证明部分保存在JWT中与用户ID关联,并在认证时进行确认是否为新发行的JWT。
基本上,使用JWT的情况下,也有观点认为重要数据不应该被管理。因此,当客户端与Firebase连接并进行身份验证时,附上新获得的JWT并访问服务器时,JWT-AuthO会识别出该请求处于认证状态,并且无论JWT是否是最新的,只要JWT被发送过来就会被认为是授权状态。
换句话说,当需要访问重要数据时,客户端始终会进行与Firebase连接的身份验证。
(关于这个JWT的问题,我认为需要进行讨论…)
API 网关控制以下路由。此外,每个 API 的 Docker 镜像和 Git 如下所示。
※ API 网关端口已设置为30000。
- path: "/:80"
- image: kwashizaki/example-vue-cli
- git: https://github.com/k-washi/example-vue-cli.git
- msg:vueにより構築したフロントエンド
- paths: ["/api/ex-golang/rest-api/", "/api/ex-golang/health/"]
- image: kwashizaki/example-golang-rest-api
- git: https://github.com/k-washi/example-golang-rest-api.git
- msg: REST-API1が提供するAPI(GET, POSTで文を提供、保存, & healthでstatus 200を返答)
- paths: ["/api/ex-jwt/jwt/ex-jwt-auth", "/api/ex-jwt/auth/ex-authentication"]
- images: kwashizaki/example-golang-jwt-auth-client:v1.0.0
- git: https://github.com/k-washi/example-golang-jwt-auth/tree/master/testApp
- msg: REST-API2が提供するJWT-AuthOによるJWT検証を伴うユーザー情報の提供
- paths: ["/ex-jwt-sr/"] #gRPC
- image: kwashizaki/example-golang-jwt-auth-server:v1.0.0
- git: https://github.com/k-washi/example-golang-jwt-auth
- msg: JWT-AuthOにおけるJWT検証( gRPCサーバー)
技术使用概述
接下来,我将介绍使用的技术。请参考我之前写的文章中的链接。
我个人非常喜欢Golang的文章,一定要看一下哦!
Vue.js 是一种流行的JavaScript 框架。
现在是JavaScript框架中很受欢迎的一种。个人而言,我觉得学习Redux-saga作为React的异步处理方式的成本很高,同时Vue.js的使用感觉也更好,所以我选择了Vue.js。
我使用Vue.js来创建前端的单页面应用(SPA)。在路由方面使用了vue-router,在管理全局状态,如用户名等方面使用了Vuex,在UI组件框架方面使用了Vuetify。
# vue --version
3.10.0
基本上,我正在使用已擴展的實現方法,該方法基於以下我過去的文章。(Git: https://github.com/k-washi/example-vue-cli.git)
Qiita:使用Vue.js(包括Vuex、vue-router和vuetify)和Firebase開始進行用戶管理。
Go语言
我一直使用Python,但想尝试一下静态类型语言,所以选择了最近流行的Golang作为后端语言。
它很容易编写并行处理代码,并且有强大的标准包支持。
在这个系统中,我们将每个服务都进行了容器化,这样可以轻松实现容器的轻量化。
例如,该次使用Golang开发的服务可以以约20MB大小进行Docker容器化,具体如下。
REPOSITORY TAG IMAGE ID CREATED SIZE
kwashizaki/example-golang-jwt-auth-server v1.0.0 4cf9b4595b01 7 hours ago 23.9MB
kwashizaki/example-golang-jwt-auth-client v1.0.0 baa4bce6c5fe 44 hours ago 24.5MB
kwashizaki/example-golang-rest-api v1.0.0 8d92d819d8ad 8 days ago 22.6MB
另外,作为Web框架,我们使用了轻量简洁的gin。
本系统的API基本上是按照我之前编写的首个Golang Web应用程序的架构,一直到测试和Docker容器化都保持相同的实现方式。
Firebase is a cloud-based platform that provides services for developing and managing mobile and web applications.
Firebase是由Google提供的MBaas(移动后端即服务)之一。它具有数据库和认证功能,在本系统中,我们使用了认证功能Firebase Authentication。在兴趣爱好的程度上,可以免费使用。
Firebase Authentication在前端认证完成后会返回JWT(Json Web Token)。
服务器可以使用前端发送的JWT来验证Firebase,判断是否为有效的JWT。
换句话说,服务器可以使用JWT来验证是否为已登录的用户。
在服务器端,需要定义一个名为ex-firebase-auth-firebase-adminsdk-xxxx.json的文件,该文件包含验证所需的参数。这是由Firebase SDK提供的。
另外,使用Golang进行实现,采用了「Vue.js + Go语言 + Firebase」的开发方式!参考了在前端和后端API都进行安全认证的SPA开发实践!
使用中文進行一個選項的重新述說是:
JSON Web令牌 (JWT)
这是一个按照以下格式构成的字符串。
{base64エンコードしたhead1er}.{base64エンコードしたclaims}.{署名}
Firebase是本次发布的发行者,可以使用密钥对JSON进行签名,以将其作为令牌处理。此外,可以使用密钥进行验证以侦测改进。
在claims部分中,可以包含任意的信息,如用户名。
我创建了一个库,名为k-washi/jwt-decode,用于解析JWT并提取其中的claims部分,然后解码Firebase中的信息。
Qiita:使用Golang进行Firebase身份验证中JWT解析
nginx -> nginx
一种用于操作系统、应用软件和应用之间的中间件,用于在发送HTTP请求等时返回响应的Web服务器软件。它具有反向代理和负载均衡功能,并且相较于Apache,具有更快速和高强度的附加功能。
我們使用Web伺服器作為分發SPA的平台。
Qiita: Vue.js项目中nginx配置和使用Docker容器化的示例
gRPC 区块链。
gRPC是Google开发的一种协议,用于实现远程过程调用(RPC)。它使用Protocol Buffers来对数据进行序列化,具有实现高速通信的特点。关于gRPC,参考了“gRPC是什么?”这篇文章。
本系统中,在后端使用了服务之间的通信。
QIITA:使用Golang开始gRPC
Docker
通过利用主机的内核进行隔离,例如隔离进程和用户,使其看起来像是在运行另一台机器。因此,可以轻松、快速地启动、停止虚拟环境。
我在之前写的一篇题为《我的第一个Golang Web应用程序:从测试到Docker容器化》的文章中介绍了如何将Golang应用程序容器化为Docker。
以下是本次使用的DockerImage。
REPOSITORY TAG IMAGE ID CREATED SIZE
kwashizaki/example-vue-cli v1.0.0 7d1f0394bec3 2 hours ago 25.3MB
kwashizaki/example-golang-jwt-auth-server v1.0.0 4cf9b4595b01 7 hours ago 23.9MB
kwashizaki/example-golang-jwt-auth-client v1.0.0 baa4bce6c5fe 44 hours ago 24.5MB
kwashizaki/example-golang-rest-api v1.0.0 8d92d819d8ad 8 days ago 22.6MB
quay.io/datawire/ambassador 0.83.0 d8caf63d933c 3 weeks ago 691MB
Kubernetes(K8s)
这是一个设计用于自动部署、扩展和应用容器的操作自动化的开源平台。
在本文中,我们将介绍这个系统的k8s配置方法。
大使
从公式中可以得知
大使是用于微服务的API网关。
功能
-
- AWS APIGatewayのようなAPIGatewayのホスティング機能
-
- Kongのような伝統的なAPIGateway機能
- Nginxや, Envoy, k8sのIngressのようなProxy機能
详细功能
-
- 細かなルーティング制御、正規表現ベースのルーティング、ホストルーティングなどが可能
-
- 認証
-
- gRPC, HTTP/2をサポート
-
- カナリアリリース
-
- シャドートラッキング機能
- 特定サービスへのL7トラフィックの透過的な監視
从运维人员的角度来看
-
- ルーティングとスケーリングをEnvoyとKubernetesに依存しているので、展開と操作が簡単
-
- TLSTerminationとリダイレクトを広範囲に運用可能
-
- トラブルシューティング時に統合的に診断する事が可能
-
- 複数の異なるバージョンのAmbassadorを運用出来て、簡単にテスト、更新する事が可能
- Istioと連携してサービスメッシュ化が可能
内部实现
-
- k8sを最大限に利用し、信頼性(reliability), 可用性(availability), スケーラビリティ(scalability)を担保
-
- 状態管理の為に、データベースのようなストレージを必要とせず、Kubernetes内に全ての状態管理を維持
-
- スケール時は、k8sのReplicasetでレプリカ数を変更するか、horizontal pod autoscalerを利用する事で簡単にスケール事が可能
- EnvoyProxyを使用して、全てのトラフィックのルーティングとプロキシーを実行
程序及其版本
# go version
go version go1.12.7 darwin/amd64
# docker -v
Docker version 19.03.2, build 6a30dfc
# kubectl version
>lient Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.6", GitCommit:"96fac5cd13a5dc064f7d9f4f23030a6aeface6cc", GitTreeState:"clean", BuildDate:"2019-08-19T11:13:49Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.6", GitCommit:"96fac5cd13a5dc064f7d9f4f23030a6aeface6cc", GitTreeState:"clean", BuildDate:"2019-08-19T11:05:16Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}
# vue --version
3.10.0
k8s清单文件
本章将启动以下的pod、service和deployments。
如您所见,该服务在localhost:30000上设有External IP。
另一方面,
kubectl get pods
NAME READY STATUS RESTARTS AGE
ambassador-55d75bc95b-29ckv 1/1 Running 0 3m59s
ambassador-55d75bc95b-2lxzv 1/1 Running 0 3m27s
ambassador-55d75bc95b-9vjnz 1/1 Running 0 2m18s
ex-go-5c5747dbdb-ddkpx 1/1 Running 1 5d21h
ex-jwt-cl-576556588d-7sdgc 1/1 Running 1 5d21h
ex-jwt-sr-78bdf9f4d4-6dmft 1/1 Running 1 5d21h
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ambassador LoadBalancer 10.97.238.197 localhost 30000:30685/TCP 5d21h
ambassador-admin NodePort 10.103.162.214 <none> 8877:31915/TCP 5d21h
ex-go ClusterIP 10.101.114.104 <none> 8080/TCP 5d21h
ex-jwt-cl ClusterIP 10.106.203.56 <none> 8080/TCP 5d21h
ex-jwt-sr ClusterIP 10.99.209.88 <none> 8080/TCP 5d21h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 92d
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
ambassador 3/3 3 3 5d22h
ex-go 1/1 1 1 5d22h
ex-jwt-cl 1/1 1 1 5d22h
ex-jwt-sr 1/1 1 1 5d22h
秘密功能
使用k8s的秘密功能,设置Firebase配置文件等机密信息,以防泄露。
google_app_creds=/tmp/ex-firebase-auth-firebase-adminsdk-xxxxxxx.json
创建文件
kubectl create secret generic --save-config firebase-secret --from-env-file ./env/env-secret.txt
请使用以下命令读取文件配置。
确认:
kubectl get secret
#NAME TYPE DATA AGE
#firebase-secret Opaque 1 5d21h
当在此处将文件路径设置为DockerImage部署时,将复制Firebase SDK的配置文件。
API Gateway Ambassador的配置
使用 k8s 进行 RBAC 管理各种资源的访问权限。假设启用了该功能,我们可以安装官方提供的清单文件。
kubectl apply -f https://getambassador.io/yaml/ambassador/ambassador-rbac.yaml
我们将实际使用的程序放在了Git的example-k8s-ambassador/ambassador/ambassador-rbac.yaml。
基本的来说,
1. 通过ClusterRoleBinding将名为ambassador的服务账号和服务等授予权限的ClusterRole进行关联。
2. 使用容器ambassador创建具有端口号(http:80,admin:8877)的Deployment,并设置副本数为3。
3. 创建一个服务,并将端口(admin:8877)关联到NodoPort上。
由于创建了一个Service作为NodePort,所以以下配置了LoadBalancer。
HTTP端口被覆盖为30000,并将容器接受的端口设置为8080。
kubectl apply -f ambassador/ambassador-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: ambassador
spec:
type: LoadBalancer
#externalTrafficPolicy: Local
ports:
- name: http
port: 30000
targetPort: 8080
selector:
service: ambassador
配置地图
我在k8s内设置了要使用的变量。
接下来,我将部署每个服务,将这些变量用作部署时的参数。
kubectl apply -f example-golang-vue/example-jwt-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ex-jwt-map
data:
origin.host: localhost
#数字は, ""で囲む
origin.port: "80"
ambassador.host: ex-jwt-sr
ambassador.port: "8080"
jwtserver.host: localhost
jwtserver.port: "50051"
容器部署
在getambassador.io/config上进行了路由配置。
在那里,通过Ambassador进行配置,将其设置为使用http://ex-go:8080进行请求。
这个ex-go是根据Deployment进行匹配的。
kubectl apply -f example-golang-vue/example-golang.yaml
apiVersion: v1
kind: Service
metadata:
name: ex-go
annotations:
getambassador.io/config: |
---
apiVersion: ambassador/v0
kind: Mapping
name: ex-health-map
prefix: /api/ex-golang
service: http://ex-go:8080
spec:
type: ClusterIP
ports:
- name: cl-ip-ex-go
port: 8080
targetPort: 8080
selector:
app: ex-go
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ex-go
spec:
replicas: 1
selector:
matchLabels:
app: ex-go
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: ex-go
spec:
containers:
- name: ex-go-ui
image: kwashizaki/example-golang-rest-api:v1.0.0
ports:
- name: ex-go
containerPort: 8080
resources:
limits:
cpu: "0.1"
memory: 100Mi
他的服务将部署清单也部署。其他服务也使用与上述清单相同的设置。
作为容器内的环境变量,它们被配置在env下面。
另外,在部署时,通过将卷下的路径文件复制到卷挂载的路径上,来进行Firebase设置。
这样一来,就不再需要在Docker镜像中包含需要保密的配置文件了。
关于gRPC,路由控制非常特殊,不需要编写getambassador.io/config服务的ex-jwt-sr方案。
此外,/jwtauth.JwtService/在一个将gRPC协议转换为golang的文件中有所记录。
kubectl apply -f example-golang-vue/example-jwt-server.yaml
kubectl apply -f example-golang-vue/example-jwt-client.yaml
apiVersion: v1
kind: Service
metadata:
name: ex-jwt-cl
annotations:
getambassador.io/config: |
---
apiVersion: ambassador/v0
kind: Mapping
name: ex-jwt-cl
prefix: /api/ex-jwt
service: http://ex-jwt-cl:8080
spec:
type: ClusterIP
ports:
- name: cl-ip-ex-jwt
port: 8080
targetPort: 8080
selector:
app: ex-jwt-cl
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ex-jwt-cl
spec:
replicas: 1
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: ex-jwt-cl
spec:
containers:
- name: ex-jwt-ui
image: kwashizaki/example-golang-jwt-auth-client:v1.0.0
ports:
- name: ex-jwt-cl
containerPort: 8080
resources:
limits:
cpu: "0.1"
memory: 100Mi
env:
- name: ORIGIN_HOST
valueFrom:
configMapKeyRef:
name: ex-jwt-map
key: origin.host
- name: ORIGIN_PORT
valueFrom:
configMapKeyRef:
name: ex-jwt-map
key: origin.port
- name: AMBASSADORHOST
valueFrom:
configMapKeyRef:
name: ex-jwt-map
key: ambassador.host
- name: PORT
valueFrom:
configMapKeyRef:
name: ex-jwt-map
key: ambassador.port
apiVersion: v1
kind: Service
metadata:
name: ex-jwt-sr
annotations:
getambassador.io/config: |
---
apiVersion: ambassador/v0
kind: Mapping
name: ex-jwt
grpc: True
prefix: /jwtauth.JwtService/
rewrite: /jwtauth.JwtService/
service: ex-jwt-sr
spec:
type: ClusterIP
ports:
- name: cl-ip-ex-jwt-sr
port: 8080
targetPort: 50051
selector:
app: ex-jwt-sr
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ex-jwt-sr
spec:
replicas: 1
selector:
matchLabels:
app: ex-jwt-sr
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: ex-jwt-sr
spec:
containers:
- name: ex-jwt-sr
image: kwashizaki/example-golang-jwt-auth-server:v1.0.0
ports:
- name: ex-jwt-sr-api
containerPort: 50051
resources:
limits:
cpu: "0.1"
memory: 100Mi
env:
- name: AMBASSADORHOST
valueFrom:
configMapKeyRef:
name: ex-jwt-map
key: jwtserver.host
- name: PORT
valueFrom:
configMapKeyRef:
name: ex-jwt-map
key: jwtserver.port
- name: GOOGLE_APPLICATION_CREDENTIALS
valueFrom:
secretKeyRef:
name: firebase-secret
key: google_app_creds
volumeMounts:
- name: firebase-creds
mountPath: /tmp #firebase-auth-credファイルを置く場所
readOnly: true
volumes:
- name: firebase-creds
hostPath:
path: /Users/washizakikai/DevLocal/git/kwashi/example-k8s-ambassador/env
总结
在这里,我们展示了使用Kubernetes进行容器管理和API Gateway(即ambassador)的设置,并且展示了一个演示微服务之间协作的示例。
为了避免与其他的Qiita文章重复,并着重说明了与Kubernetes清单相关的内容。
如果您对Vue或Golang的配置感到好奇,请务必在文章的中途查看所参考的文章。