说明: apollo官网已经说明的很清楚 在这里记录仅为了记录 方便日后回顾
什么是Apollo
Apollo(阿波罗)是一款可靠的分布式配置管理中心,诞生于携程框架研发部,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
Apollo支持4个维度管理Key-Value格式的配置:
application (应用)
environment (环境)
cluster (集群)
namespace (命名空间)
同时,Apollo基于开源模式开发,开源地址:https://github.com/ctripcorp/apollo
Apollo的特点
- 统一管理不同环境,不同集群的配置
- 配置实时更新
- 版本发布
- 灰度发布
- 权限管理
Apollo的核心
- application 应用
- environment 环境
- cluster 集群
- namespace 命名空间
Apollo为什么采用Eureka
- Eureka是业界比较成熟的工具 并且是开源的 方便排查问题
- 由于携程本身主要使用Java的SpringCloud和Boot 所以和Eureka集成更方便
使用Docker安装Apollo
-
安装Java jdk 1.8
-
查找dockerhub中mysql镜像
docker search mysql
-
拉取docker的mysql镜像
docker pull mysql:5.7
-
运行mysql镜像
docker run -d -p 13306:3306 --privileged=true -v /mnt/mysql/conf/mysql.conf:/etc/mysql/mysql.conf.d/mysqld.cnf -v /mnt/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
-
run run 是运行一个容器
-d 表示后台运行
-p 表示容器内部端口和服务器端口映射关联
–privileged=true 设值MySQL 的root用户权限, 否则外部不能使用root用户登陆
-v /docker/mysql/conf/my.cnf:/etc/my.cnf 将服务器中的my.cnf配置映射到docker中的/docker/mysql/conf/my.cnf配置
-v /docker/mysql/data:/var/lib/mysql 同上,映射数据库的数据目录, 避免以后docker删除重新运行MySQL容器时数据丢失
-e MYSQL_ROOT_PASSWORD=123456 设置MySQL数据库root用户的密码
–name mysql 设值容器名称为mysql
mysql:5.7 表示从docker镜像mysql:5.7中启动一个容器
–character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci 设值数据库默认编码
-
进入mysql容器
docker exec -it 1de812984103 bash
-
1de812984103 容器id
exec 在运行的容器中执行命令
-it 以交互模式执行
bash 脚本类型
-
连接mysql
-
mysql -u root -p
-
在这里我们直接使用连接时指定的密码进行登录即可 后面如果要更改密码 可参考mysql命令文档进行操作
-
-
导入apollo系统数据
- 直接使用apollo内部提供好的sql文件进行导入即可
- apolloportaldb.sql
- apolloconfigdb.sql
- 在这里笔者直接使用mysql workbench工具进行导入了
- 直接使用apollo内部提供好的sql文件进行导入即可
-
下载apollo服务镜像
- dockerhub地址
-
docker pull apolloconfig/apollo-portal:latest
docker pull apolloconfig/apollo-adminservice:latest
docker pull apolloconfig/apollo-configservice:latest -
apollo-adminservice //Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
apollo-configservice //Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
apollo-portal //Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试
-
运行镜像
-
在这里为了避免和其他服务端口冲突 所以使用8081 8091 8071来对应容器端口
-
Apollo Config Service
docker run -p 8081:8080 \ -e SPRING_DATASOURCE_URL="jdbc:mysql://localhost:13306/ApolloConfigDB?characterEncoding=utf8" \ -e SPRING_DATASOURCE_USERNAME=root -e SPRING_DATASOURCE_PASSWORD=123456 \ -d -v /tmp/logs:/opt/logs --name apollo-configservice apolloconfig/apollo-configservice
-
Apollo Admin Service
docker run -p 8091:8090 \ -e SPRING_DATASOURCE_URL="jdbc:mysql://localhost:13306/ApolloConfigDB?characterEncoding=utf8" \ -e SPRING_DATASOURCE_USERNAME=root -e SPRING_DATASOURCE_PASSWORD=123456 \ -d -v /tmp/logs:/opt/logs --name apollo-adminservice apolloconfig/apollo-adminservice
-
Apollo Portal
docker run -p 8071:8070 \ -e SPRING_DATASOURCE_URL="jdbc:mysql://localhost:13306/ApolloPortalDB?characterEncoding=utf8" \ -e SPRING_DATASOURCE_USERNAME=root -e SPRING_DATASOURCE_PASSWORD=123456 \ -e APOLLO_PORTAL_ENVS=pro \ -e PRO_META=http://localhost:8081 \ -d -v /tmp/logs:/opt/logs --name apollo-portal apolloconfig/apollo-portal
-
参数说明
-
SPRING_DATASOURCE_URL: 对应环境ApolloConfigDB的地址
SPRING_DATASOURCE_USERNAME: 对应环境ApolloConfigDB的用户名
SPRING_DATASOURCE_PASSWORD: 对应环境ApolloConfigDB的密码
APOLLO_PORTAL_ENVS(可选): 对应ApolloPortalDB中的apollo.portal.envs配置项,如果没有在数据库中配置的话,可以通过此环境参数配置
DEV_META/PRO_META(可选): 配置对应环境的Meta Service地址,以${ENV}_META命名,需要注意的是如果配置了ApolloPortalDB中的apollo.portal.meta.servers配置,则以apollo.portal.meta.servers中的配置为准
-
-
-
运行结果
-
访问
-
apollo登录默认用户名和密码 apollo admin
-
添加用户
-
添加部门
-
key: organizations
value: [{“orgId”:“测试部门”,“orgName”:“测试部门”}]
-
NodeJs对接Apollo
NestJs
Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用 JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。
ctrip-apollo
用于支持nodejs对接apollo的插件
npm i ctrip-apollo
Dockerfile
使用docker容器进行构建并启动的执行脚本
# 获取node镜像
FROM node:12
# 创建工作目录
RUN mkdir app
# 设置工作目录
WORKDIR /app
# 拷贝目录
COPY . /app
# 镜像的维护者
MAINTAINER zhangzw
# 安装npm包
RUN npm --registry https://registry.npm.taobao.org i
# 安装全局pm2
RUN npm --registry https://registry.npm.taobao.org i -g pm2
# 编译
RUN npm run build
# 容器端口
EXPOSE 3000
RUN mkdir -p /usr/share/zoneinfo/Asia/
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' >/etc/timezone
RUN dpkg-reconfigure -f noninteractive tzdata
# 配置环境变量
ENV NODE_ENV production
# 设置时区
ENV TZ "Asia/Shanghai"
# 启动命令
CMD ["pm2-runtime", "start", "dist/src/main.js", "-i", "2"]
.production.env
用于区分环境 在nestjs启动时 绑定环境变量
APOLLO_CLUSTER=v-test
APOLLO_HOST=http://localhost:8081
APOLLO_APP_ID=server
APOLLO_NAMESPACES=application,config
APP_ID=nestjs
APP_PORT=3000
NODE_ENV=production
AppEnvModule
使用nestjs配置模块绑定全局env变量
NODE_ENV是在启动时指定的 由于我本地使用的是docker 所以在启动是NODE_ENV的值就是production了
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
const NODE_ENV = process.env.NODE_ENV;
@Global()
@Module({
imports: [
ConfigModule.forRoot(
{
envFilePath: NODE_ENV === 'production' ? '.production.env' : '.dev.env',
isGlobal: true,
}
),
],
})
export class AppEnvModule { }
apollo.service.ts
用于初始化apollo连接和获取配置函数
@Injectable({ scope: Scope.DEFAULT }) // 该实例多个类之间可进行共享
lock // 避免重复初始化配置的标记
Log // 封装好的日志组件 内部使用的是log4js
import { Injectable, Scope } from '@nestjs/common';
import * as apollo from 'ctrip-apollo';
import { Log } from 'src/log';
import * as bluebird from 'bluebird';
import { IApolloVariable } from './apollo.interface';
@Injectable({ scope: Scope.DEFAULT })
export class ApolloService {
private apolloVariable: IApolloVariable;
private apolloClient: any;
private lock = false;
constructor(
private readonly log: Log,
) {
this.log.setContext(ApolloService.name);
}
// 获取apollo配置
public async getApolloVariable (): Promise<IApolloVariable> {
if (this.apolloVariable) {
return this.apolloVariable;
}
// 解决异步获取配置后 但没有及时返回造成的重复获取问题
if (this.lock) {
this.apolloVariable = await this.waitGetRealApolloVariable();
return this.apolloVariable;
}
this.apolloVariable = await this.getRealApolloVariable();
return this.apolloVariable;
}
// 获取远程apollo配置
private async getRealApolloVariable (): Promise<IApolloVariable> {
this.lock = true;
let variable: any = {};
const { APOLLO_HOST, APOLLO_CLUSTER, APOLLO_NAMESPACES, APOLLO_APP_ID } = process.env;
this.apolloClient = apollo({ host: APOLLO_HOST, appId: APOLLO_APP_ID, cluster: APOLLO_CLUSTER });
try {
await bluebird.Promise.map(APOLLO_NAMESPACES.split(','), async (nsName) => {
const ns = this.apolloClient.namespace(nsName);
let conf = await ns.ready();
conf = conf.config();
this.log.info(`${nsName} 配置:${JSON.stringify(conf)}`);
for (const k of Object.keys(conf)) {
if (variable[k]) {
throw new Error('apollo配置重复:' + k);
}
}
variable = { ...variable, ...conf };
this.listenNs(ns, nsName);
});
} catch (error) {
this.log.error('获取apollo配置出错,退出.', error);
process.exit(1);
}
return variable;
}
// 等待获取配置
private async waitGetRealApolloVariable (): Promise<IApolloVariable> {
return new Promise(async (resolve) => {
const timer = setInterval(async () => {
try {
this.log.info('apollo 等待获取配置中', this.apolloVariable);
if (this.apolloVariable) {
clearInterval(timer);
return resolve(this.apolloVariable);
}
} catch (error) {
this.log.error(`apollo 等待获取配置异常 ${error}`);
}
}, 100);
});
}
// 监听配置变更并实时更新
private listenNs (ns, nsName) {
ns.on('change', ({ key, oldValue, newValue }) => {
this.log.info(
`apollo配置变更: ns【${nsName}】key【${key}】从【${oldValue}】变为【${newValue}】`,
);
this.apolloVariable[key] = newValue;
});
ns.on('add', ({ key, value }) => {
this.log.info(
`apollo配置新增: ns【${nsName}】key【${key}】值【${value}】`,
);
if (this.apolloVariable[key]) {
this.log.error(`新增失败,配置重复 ${key}`, '');
return;
}
this.apolloVariable[key] = value;
});
// do nothing
ns.on('delete', ({ key, value }) => {
this.log.info(
`apollo配置删除: ns【${nsName}】key【${key}】值【${value}】`,
);
});
}
}