SpringCloud微服务部署环境参数动态设置?

SpringCloud微服务运维最佳实践,如何动态定义各种启动参数?

笔者是一名Java服务端程序员,学习微服务后,在部署时发现过程复杂,且做的重复工作非常多,因此学习基本运维。目标是,使用Kubernetes和容器技术进行微服务编排和部署。

本文只讨论服务容器化,不涉及K8s和Jenkins相关内容。

问题概述

在服务容器化时,一些参数必须动态传入,以适应不同的部署环境。变化的参数包括:

  • 部署环境(开发环境、测试环境、预发环境、生成环境)
  • 服务版本(0.0.1、1.0.2、1.0.1-beta)
  • 服务发现(部署时将服务注册到注册中心如Nacos)

我的Java项目(Example)由多个微服务构成:如Example-core,Example-auth,Example-gateway等。以Example-auth这个微服务作为切入点,打包为Docker容器的代码为:

# 使用官方的OpenJDK 17作为基础镜像

FROM openjdk:17

# 镜像环境变量:

# 开发环境:dev

# 测试环境:test

# 预发环境:staging

# 生产环境:prod, 默认生产环境

ARG ENVIRONMENT=prod

# Jar包的版本,默认0.0.1-SNAPSHOT

ARG JAR_VERSION=0.0.1-SNAPSHOT

# 注册中心服务地址

ARG SERVER_NAME=www.nacos-server.cn

# 维护者

MAINTAINER xlxing@bupt.edu.cn

# 拷贝文件到Docker容器中

COPY target/auth-${JAR_VERSION}.jar /app/auth-${JAR_VERSION}.jar

# 暴露服务端口

EXPOSE 8999

# 容器启动项

ENTRYPOINT ["java", "-jar", "/app/auth-${JAR_VERSION}.jar", "--spring.profiles.active=${ENVIRONMENT}", "--spring.cloud.nacos.discovery.ip=${SERVER_NAME}"]

在构建容器时可以动态传入参数(jar包版本为1.1.0,环境是dev,服务发现地址www.my-auth.cn,构建的容器命名为myapp/example:auth-dev-1.1.0):

docker build --build-arg JAR_VERSION=1.1.0 ENVIRONMENT=dev SERVER_NAME=www.my-auth.cn -t myapp/example:auth-dev-1.1.0 .

该内容产生了以下两个具体问题:

1. 如何动态获取JAR_VERSION

在构建项目时,仍然需要手动传入项目版本,实际上该信息存在于项目中,如该微服务auth的pom.xml文件:

    <artifactId>auth</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>auth</name>

<description>权限微服务</description>

理想情况是:后端程序员来定义项目的版本,而流水线只需要依赖项目即可。

但是在当前情况下:当auth项目的version变更时,构建Docker镜像的指令也需要同步变化。该问题可以总结为,构建容器时如何从项目中获取JAR_VERSION?

docker build --build-arg JAR_VERSION=1.1.0 ENVIRONMENT=dev SERVER_NAME=www.my-auth.cn -t myapp/example:auth-dev-1.1.0 .

2. 如何自动获取宿主机的SERVER_NAME

微服务启动时,将该服务注册到Nacos上,声明自己的服务地址。当部署到具体的服务器上时,该地址才被绑定,如auth服务可以部署在server-a上,也可以部署到server-b上。如何在运行时确定服务地址而不是构建时

补充内容:
auth服务的配置文件

spring:

application:

name: auth

# Nacos配置

cloud:

nacos:

server-addr: www.nacos-server.cn

# 服务发现

discovery:

cluster-name: BEIJING

# 设置为非临时实例

ephemeral: true

# 设置命名空间

namespace: e35500e1-2441-4001-b60f-3f7d55bxxxxx

# 配置中心

config:

file-extension: yaml # 文件后缀名

namespace: e35500e1-2441-4001-b60f-3f7d55bxxxxx

group: DEFAULT_GROUP

dubbo:

# 将Dubbo注册到Nacos中,这样可供消费者直接使用

application:

name: dubbo-auth

protocol:

name: dubbo

port: -1

registry:

address: nacos://www.nacos-server.cn?namespace=228df068-54b7-405e-9e32-72c759d79ed9

group: DEV_DUBBO_GROUP

拓展问题:使用K8s和容器在分布式环境下部署微服务项目最佳实践?

其中涉及到很多配置相关的问题,缺乏一个系统性的文章探讨如何部署和运维。

一些尝试:

  1. 我有两台服务器A和B,我在部署前已经计划将auth服务部署到B上,因此构建时参数SERVER_NAME=B,构建好的容器无法直接在其他服务器上启动。
    一条可行的方法是,在构建容器时不指定SERVER_NAME,在docker run时动态传入参数。
    ARG SERVER_NAME=www.nacos-server.cn 改为ENV SERVER_NAME=www.nacos-server.cn
  2. JAR_VERSION目前是手动更改。

第一个问题:
我们在构建开发流水线的时候,会写一个脚本(比如Jenkins)。类似于这样的一个过程:

  1. 从pom.xml中获取version值,记为VERSION
  2. 使用脚本更改Dockerfile中的JAR_VERSION,此时Dockerfile中的JAR_VERSION=${VERSION}
  3. 生成新的Docker Image,例如 docker build -t myapp/auth:${VERSION} .

总的来说,我们无法直接在Dockerfile中获取pom.xml中的VERSION,但是可以在流水线脚本中往Dockerfile中插入静态值。

第二个问题:
我们可以将流水线部署分为两个部分,构建部署
你的回复没有解决部署时的环境变量问题,SERVER_NAME是与所属的服务器相关的,在使用K8s时构建的配置文件如k8s-prod.yaml中,env也是一些动态参数。使用环境变量只是解决了可以在运行时变更参数的作用。
比如k8s架构有3个子节点k8s-node01、k8s-node02、k8s-node03,在部署的时候将auth这个微服务部署到哪一个节点,对运维工程师来说是透明的。
此时想到了一个笨重的方法:在k8s-node01、k8s-node02、k8s-node03的系统中提前准备好环境变量MY_IPV4,然后在docker run的时候参数除了在k8s的yaml文件中定义,也可以从服务器的环境变量中获取。例如,gateway将auth服务路由到lb://auth,而lb://auth信息是从注册中心拿到的,如http://111.229.38.208:8999
在传统的微服务部署中,我可以基本使用nacos作为注册中心使用。K8s中的注册中心原理是DNS服务器。我现在尝试将传统微服务半自动化部署迁移到k8s部署上来,对于服务注册与发现可能有一些理解不充分。希望前辈指正。
主要问题是:传统部署微服务时,微服务启动时总是将自身(自身所在节点的ip)注册到Nacos,如何使得微服务部署是无状态的。

spring:

application:

name: gateway

cloud:

# SpringCloudGateway配置

gateway:

default-filters:

# http请求的Header中增加 k, v

- AddRequestHeader=gateway-env, gateway-dev

# 前端个请求uri会拼接一个版本前缀发送到特定的服务

routes:

- id: auth

uri: lb://auth

filters:

# 服务版本前缀

- PrefixPath=/api/v1

predicates:

- Path=/auth/**

- id: multimedia

uri: lb://multimedia

filters:

- PrefixPath=/api/v1

predicates:

- Path=/multimedia/**

- id: core

uri: lb://core

filters:

- PrefixPath=/api/v1

predicates:

- Path=/core/**

# Gateway不使用Servlet而是WebFlux

main:

web-application-type: reactive

# 微服务分布式鉴权专用Redis

# Gateway和Auth共享一个权限管理Redis数据库

data:

redis:

client-type: lettuce

database: 0

host: www.xingxiaolin.cn

port: 6379

lettuce:

pool:

enabled: true

max-active: 8

max-idle: 8


回答:

更新:
我大概理解了你的问题了。

  1. 你的问题应该是服务注册到nacos的时候,需要把实例的IP端口信息提交上去。问题出在每次部署的时候你需要手动指定这个上报的IP端口,比较麻烦。

这里可能会有问题:

  1. gateway在转发请求到其他服务时,应该走“内网”传输。也就是服务应该使用内网IP去注册。所以你可以在启动的时候直接拿到节点的IP/端口(这个是完全可行的,和是不是传统部署都没有关系)。
  2. 不知道你是不是使用了自动获取的方式去获取实例的IP,但是注册的时候出了问题,注册的服务都是docker容器内部的172.x开头的地址,这个问题导致了gateway没法正常工作。所以你才想到了去手动指定实例的IP。通过手动指定的方式也可以实现正常注册,但是没有必要多绕一圈。

如果你要用传统的方式部署微服务

  1. 使用docker启动容器的时候,建议把网络模式改成host,这样在容器内,能直接获取到节点的IP,注册服务、在多节点的环境,gateway在转发时也都能正常工作。启动实例的时候直接 docker run --network host -d image:tag ,就不需要指定额外的参数了。
  2. docker 默认情况下是只能单个节点运行的,即使 docker-compose 也是一样的。所以在每个节点上,不管怎么样都要考虑到端口分配问题,它需要手动去维护,所以是会有一些麻烦在。
  3. 还是建议你试试用 docker swarm 来做容器编排,简单方便,虽然现在貌似用的不多,但还是docker官方推荐的生产环境部署方式之一。:https://docs.docker.com/get-started/orchestration/

问题1: 如何动态获取 VERSION
通常情况下,在构建的时候就能拿到版本号,通过脚本读取pom.xml文件(或者通过插件,但是实际上也是读取pom.xml)

问题2: 如何自动获取宿主机的SERVER_NAME
可以通过 “环境变量”


我举一个使用 docker swarm 部署微服务的例子(k8s也类似):

docker-compose.yml ,会启动 gateway / auth / core 三个服务

services:

gateway:

image: example/gateway:v1

environment: # 给这个服务添加环境变量

- ENVIRONMENT=prod #当前环境/服务名字/nacos注册信息

- SERVER_NAME=gateway

- NACOS_SERVER=nacos.domain.com

- NACOS_namespace=e35500e1-2441-4001-b60f-3f7d55bxxxxx

ports:

- "8080:8080" # 暴露网关的端口

deploy:

mode: replicated

replicas: 2 # 给这个服务部署两个实例,下面的操作都类似。

auth:

image: example/auth:v1

environment:

- ENVIRONMENT=prod

- SERVER_NAME=auth

- NACOS_SERVER=nacos.domain.com

- NACOS_namespace=e35500e1-2441-4001-b60f-3f7d55bxxxxx

deploy:

mode: replicated

replicas: 2

core:

image: example/core:v1

environment:

- ENVIRONMENT=prod

- SERVER_NAME=core

- NACOS_SERVER=nacos.domain.com

- NACOS_namespace=e35500e1-2441-4001-b60f-3f7d55bxxxxx

deploy:

mode: replicated

replicas: 2

环境变量,通常都是在“运行时”获取的,但是你可以把一些默认的环境变量,在“构建时”时写入到镜像中。

使用“ARG”也是可以的,但是感觉不太方便。

如果你已经用了微服务,就不建议用 纯docker 去手动管理容器和服务了,你应该把部署的工作交给 “容器编排系统” 来做,比如 k8s / docker-swarm 。


nacos读取配置文件的时候,是可以直接从环境变量中取值的,可以不用手动传参数。


补充一个k8s部署的例子:

apiVersion: apps/v1

kind: Deployment

metadata:

name: gateway

spec:

selector:

matchLabels:

app: gateway

template:

metadata:

labels:

app: gateway

spec:

containers:

- name: gateway

image: example/gateway:v1

resources:

limits:

memory: "128Mi"

cpu: "500m"

env:

- name: ENVIRONMENT

value: prod

- name: SERVER_NAME

value: gateway

ports:

- containerPort: 8000

---

apiVersion: apps/v1

kind: Deployment

metadata:

name: auth

spec:

selector:

matchLabels:

app: auth

template:

metadata:

labels:

app: auth

spec:

containers:

- name: auth

image: example/auth:v1

resources:

limits:

memory: "128Mi"

cpu: "500m"

env:

- name: ENVIRONMENT

value: prod

- name: SERVER_NAME

value: auth

ports:

- containerPort: 8000

---

apiVersion: apps/v1

kind: Deployment

metadata:

name: core

spec:

selector:

matchLabels:

app: core

template:

metadata:

labels:

app: core

spec:

containers:

- name: core

image: example/core:v1

resources:

limits:

memory: "128Mi"

cpu: "500m"

env:

- name: ENVIRONMENT

value: prod

- name: SERVER_NAME

value: core

ports:

- containerPort: 8000

---

apiVersion: v1

kind: Service

metadata:

name: gateway

spec:

selector:

app: gateway

ports:

- port: 8000

targetPort: 8000

---

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: example-ingress

labels:

name: example-ingress

spec:

rules:

- host: example.domain.com

http:

paths:

- pathType: Prefix

path: "/"

backend:

service:

name: gateway

port:

number: 8000

以上是 SpringCloud微服务部署环境参数动态设置? 的全部内容, 来源链接: utcz.com/p/945508.html

回到顶部