部署Lagom工程到k8s

令狐宣
2023-12-01

Run a lagom project on Kubernetes

本文描述了如何将一个lagom工程部署到k8s,分如下几个部分讲述:

  1. 选择lagom的理由
  2. 准备
  3. 配置
  4. 制作镜像
  5. 发布
  6. 测试
  7. 其他说明

选择lagom的理由

Lagom framework是一个集成ES/CQRS的微服务框架,但是真正使用的时候不一定非要用CQRS,个人觉得CQRS适合在高并发维护状态的业务场景下使用。普通情况下的CRUD集成Slick或者Quill就行。

使用Lagom framework,能够快速的构建一个多微服务工程。微服务之间可以通过服务发现,方便的进行交互。借助akka cluster,每个微服务在k8s上可以方便的水平扩展。

出于这些优势,企业或者一个开发组,可以把内部所有工程当做一个lagom工程的子工程,更可以抽象出一些通用的模块,然后把工程集成在一起,方便管理,更方便服务间的交互。

目前我了解到可以使用两种方式实现微服务之间的交互:

  1. 通过Message Broker,一个服务produce数据,其他服务可以直接consume数据。
  2. 通过服务发现,直接访问接口。

准备

下载官方样例工程,我使用的最新版本是1.6.x,选在内部的shopping-cart-scala工程。

本文不使用官方提供的shell来部署发布,kafka和postgre使用已有的本地环境,一般kafka和数据库不发布在k8s上,这样更切合于线上环境情形。

IntelliJ IDEA引入工程,下载相关的依赖包,建议使用华为sbt repository。

[repositories]
  local
  huaweicloud-ivy: https://mirrors.huaweicloud.com/repository/ivy/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
  huaweicloud-maven: https://mirrors.huaweicloud.com/repository/maven/
  aliyun-ivy: http://maven.aliyun.com/nexus/content/groups/public, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
  aliyun-maven: http://maven.aliyun.com/nexus/content/groups/public
  akka-nightly: http://repo.akka.io/snapshots/
  maven-local

配置

在inventory和shopping-cart的prod-application.conf上增加kafka配置:

lagom.broker.kafka {
    # The name of the Kafka service to look up out of the service locator.
    # If this is an empty string, then a service locator lookup will not be done,
    # and the brokers configuration will be used instead.
    service-name = ""

    # The URLs of the Kafka brokers. Separate each URL with a comma.
    # This will be ignored if the service-name configuration is non empty.
    brokers = ${KAFKA_BROKERS}
}
如果是本地idea运行,只需要关注application.conf,在build.sbt上增加设置kafka,例如:lagomKafkaAddress in ThisBuild := "cs188:9099"

制作镜像

Sbt提供了方便的组件来制作docker镜像,不需要自己编写dockerfile,可以查看build.sbt中的dockerSettings信息。

工程目录下执行如下命令:

sbt docker:publishLocal

特别的,如果你不想手工制作忽略下面内容,想手动制作参考如下:shopping-cart有两个微服务,需要制作两个镜像,下面以inventory为例:

  1. 编写dockerfile,jdk根据自己环境修改

    FROM java:oracle-java-8-jdk
    MAINTAINER "lookqlp<lookqlp@126.com>"
    
    WORKDIR /test
    
    EXPOSE 8080
    
    COPY inventory/target/universal/inventory-0.1.0-SNAPSHOT.zip /test/inv.zip
    
    RUN unzip inv.zip && rm -f  inv.zip
    
    CMD java -Dhttp.port=8080  -cp /test/inventory-0.1.0-SNAPSHOT/conf/:/test/inventory-0.1.0-SNAPSHOT/lib/* play.core.server.ProdServerStart
    
  2. 修改project/plugins.sbt,注释掉dynver、scalafmt,不然打的zip包含有时间后缀。

    addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.6.1")
    // Set the version dynamically to the git hash
    //addSbtPlugin("com.dwijnand" % "sbt-dynver" % "3.3.0")
    //
    //addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.0")
    
  3. 执行,根据需要修改路径、镜像名称和版本号

    docker build -f path/dockerfile -t inventory:v1 .
    

如果k8s环境不是本机,需要将镜像存到私有的docker仓库中,下面有Docker相关命令说明

//上传到指定docker仓库
docker tag inventory:v1 myhub.xxx.com/inventory:v1
docker push myhub.xxx.com/inventory:v1
//下载镜像
docker pull myhub.xxx.com/inventory:v1
//登录
docker login myhub.xxx.com

发布

修改k8s yaml文件

inventory.yaml:

apiVersion: "apps/v1"
kind: Deployment
metadata:
  name: inventory
spec:
  replicas: 1
  selector:
    matchLabels:
      app: inventory

  template:
    metadata:
      labels:
        app: inventory
    spec:
      containers:
      - name: inventory
        image: "inventory:0.1.0-SNAPSHOT"
        env:
        - name: JAVA_OPTS
          value: "-Xms256m -Xmx256m -Dconfig.resource=prod-application.conf"
        - name: APPLICATION_SECRET
          value: "123445"
        - name: KAFKA_BROKERS
          value: "172.18.21.188:9099"

---
apiVersion: v1
kind: Service
metadata:
  name: inventory
spec:
  ports:
  - name: http
    port: 8081
    targetPort: 9000
  selector:
    app: inventory
  type: LoadBalancer

说明:

  1. apiVersion: “apps/v1beta2"发布到正式k8s环境会失败,改成"apps/v1”。
  2. image修改成自己的镜像名称
  3. KAFKA_BROKERS配置成本地环境的kafka配置
  4. APPLICATION_SECRET随意修改

shopping-cart.yaml:

apiVersion: "apps/v1"
kind: Deployment
metadata:
  name: shopping-cart
spec:
  replicas: 3
  selector:
    matchLabels:
      app: shopping-cart

  template:
    metadata:
      labels:
        app: shopping-cart
    spec:
      containers:
      - name: shopping-cart
        image: "shopping-cart:0.1.0-SNAPSHOT"
        env:
        - name: JAVA_OPTS
          value: "-Xms256m -Xmx256m -Dconfig.resource=prod-application.conf"
        - name: APPLICATION_SECRET
          value: "123456"
        - name: POSTGRESQL_URL
          value: "jdbc:postgresql://172.18.21.188/shopping_cart"
        - name: POSTGRESQL_USERNAME
          value: shopping_cart
        - name: POSTGRESQL_PASSWORD
          value: shopping_cart
        - name: KAFKA_BROKERS
          value: "172.18.21.188:9099"
        - name: REQUIRED_CONTACT_POINT_NR
          value: "3"
        ports:
          - name: management
            containerPort: 8558
        readinessProbe:
          httpGet:
            path: "/ready"
            port: management
          periodSeconds: 10
          failureThreshold: 10
          initialDelaySeconds: 20
        livenessProbe:
          httpGet:
            path: "/alive"
            port: management
          periodSeconds: 10
          failureThreshold: 10
          initialDelaySeconds: 20

---
apiVersion: v1
kind: Service
metadata:
  name: shopping-cart
spec:
  ports:
  - name: http
    port: 8080
    targetPort: 9000
    nodePort: 31234
  selector:
    app: shopping-cart
  type: LoadBalancer

同上,说明:

  1. PG相关配置
  2. Service port 8080,和inventory不一样,不然本地运行k8s单机环境会端口冲突,只能启动一个Service
  3. nodePort可填可不填,不填时随机生成,要求30000-39999
  4. replicas: 3,副本数要大于或等于REQUIRED_CONTACT_POINT_NR,参数的含义参考openshift

如果想发布到指定的namespace,参考:

// my.yaml
{
  "kind": "Namespace",
  "apiVersion": "v1",
  "metadata": {
    "name": "shopping-cart",
    "labels": {
      "name": "shopping-cart"
    }
  }
}
// 新建
kubectl apply -f my.yaml
// 切换当前空间
kubectl config set-context --current --namespace=shopping-cart

执行发布

//新建角色,rbac参考[openshift](https://developer.lightbend.com/guides/openshift-deployment/lagom/forming-a-cluster.html)
kubectl apply -f shopping-cart/deploy/specs/common/rbac.yaml

//运行,注意需要在正确的namespace下执行
//两个服务发布在相同的namespace下,不然服务间不能正常交互,报错:Service shopping-cart was not found by service locator
//可以使用-n shopping-cart来指定
kubectl apply -f shopping-cart.yaml
kubectl apply -f inventory.yaml

//删除
kubectl delete -f shopping-cart.yaml
kubectl delete -f inventory.yaml

//查看网络
kubectl get svc

//查看指定pod日志
kubectl logs podid

//查看指定组件描述信息
kubectl describe pod podid
kubectl describe service serviceid

//查看所有
kubectl get all

测试

k8s运行服务的列表:

NAME                                 READY   STATUS    RESTARTS   AGE
pod/inventory-64bdcd5cfc-rbslf       1/1     Running   0          17h
pod/shopping-cart-64468ccb66-7ffwk   1/1     Running   0          17h
pod/shopping-cart-64468ccb66-h7v2n   1/1     Running   0          17h
pod/shopping-cart-64468ccb66-jbtpw   1/1     Running   0          17h


NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/inventory       LoadBalancer   10.110.98.10     localhost     8081:30441/TCP   17h
service/shopping-cart   LoadBalancer   10.103.238.201   localhost     8080:31234/TCP     17h


NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/inventory       1/1     1            1           17h
deployment.apps/shopping-cart   4/4     4            4           17h

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/inventory-64bdcd5cfc       1         1         1       17h
replicaset.apps/shopping-cart-64468ccb66   4         4         4       17h

可以看到很多组件,有pod、service、deployment、replicaset,具体含义简单了解可以看看这篇软文

本地单节点k8s集群,端口使用配置中8080和8081,如果是正式的k8s集群,可以使用nodePort。

curl http://localhost:8080/shoppingcart/123

curl -H "Content-Type: application/json" -d '{"itemId": "456", "quantity": 2}' -X POST http://localhost:8080/shoppingcart/123

curl http://localhost:8081/inventory/456

因为部署的service是LoadBalancer模式,请求会发送到shopping-cart服务随机一个节点上。

其他说明

inventory通过服务发现,并引用,可以在工程里直接使用shoppingCartService。

  lazy val shoppingCartService = serviceClient.implement[ShoppingCartService]

message broker信息可以通过shoppingCartService.shoppingCartTopic.subscribe.atLeastOnce消费数据。

也可以通过如下方式直接调用shopping-cart接口

def reGet(itemId: String): ServiceCall[NotUsed, String] = ServiceCall {r=>
    shoppingCartService.get(itemId).invoke().map{d=>
      d.toString
    }
}
 类似资料: