使用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ù)

overview2.png

按照图示,客户端使用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的配置感到好奇,请务必在文章的中途查看所参考的文章。

广告
将在 10 秒后关闭
bannerAds