Nacos 解决 laravel 多环境下配置切换
前言
对于应用程序运行的环境来说,不同的环境有不同的配置通常是很有用的。例如,你可能希望在本地使用的缓存驱动不同于生产服务器所使用的缓存驱动。
痛点
.env
配置不能区分多环境(开发,测试,生产).env
配置共享太麻烦(团队局域网环境)- 配置不能实时管理,增删改配置
- 自动化部署配置
.env
文件过于繁琐
Nacos 简介
Nacos 是阿里巴巴最新开源的项目,核心定位是 “一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台”,项目地址:nacos.io/zh-cn/
应用
这里主要使用了 Nacos
的配置管理,并没有使用到动态服务等功能。原理也很简单,通过接口直接修改 .env
文件。Nacos 服务可以直接使用使用阿里云提供的 应用配置管理
,无须安装。链接如下: acmnext.console.aliyun.com/
代码
<?php
namespace App\Console\Commands;
use GuzzleHttp\Client;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Validator;
class NacosTools extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'nacos {action?}';
private $accessKey;
private $secretKey;
private $endpoint = 'acm.aliyun.com';
private $namespace;
private $dataId;
private $group;
private $port = 8080;
private $client;
private $serverUrl;
/**
* The console command description.
*
* @var string
*/
protected $description = 'Nacos 管理工具';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
* @throws \Exception
*/
public function handle()
{
$this->accessKey = env('NACOS_ACCESS_KEY');
$this->secretKey = env('NACOS_SECRET_KEY');
$this->endpoint = env('NACOS_ENDPOINT');
$this->namespace = env('NACOS_NAMESPACE');
$this->port = env('NACOS_PORT', $this->port);
$this->dataId = env('NACOS_DATA_ID');
$this->group = env('NACOS_GROUP');
if (!$this->validate()) {
$this->error('请检查配置参数');
return;
}
$this->client = new Client(['verify' => false]);
$this->info('Nacos 配置工具');
$actions = [
'获取配置',
'发布配置',
'删除配置',
];
if (is_null($this->argument('action'))) {
$action = $this->choice('请选择操作',
$actions,
$actions[0]);
} else {
if (in_array($this->argument('action'), array_keys($actions))) {
$action = $actions[$this->argument('action')];
} else {
$action = $this->choice('请选择操作',
$actions,
$actions[0]);
}
}
$this->do($action);
}
public function do($action = '获取配置')
{
switch ($action) {
default:
case '获取配置':
$config = $this->getConfig();
if ($config) {
file_put_contents('.env', $config);
$this->info('获取配置成功');
} else {
$this->error('获取配置失败');
}
break;
case '发布配置':
if ($this->publishConfig()) {
$this->info('发布配置成功');
} else {
$this->error('发布配置失败');
}
break;
case '删除配置':
if ($this->removeConfig()) {
$this->info('删除配置成功');
} else {
$this->error('删除配置失败');
}
break;
}
}
/**
* 验证配置参数
*
* Date: 2020/6/10
* @return bool
*/
private function validate()
{
$data = [
'accessKey' => $this->accessKey,
'secretKey' => $this->secretKey,
'endpoint' => $this->endpoint,
'namespace' => $this->namespace,
'dataId' => $this->dataId,
'group' => $this->group,
];
$rules = [
'accessKey' => 'required',
'secretKey' => 'required',
'endpoint' => 'required',
'namespace' => 'required',
'dataId' => 'required',
'group' => 'required',
];
$messages = [
'accessKey.required' => '请填写`.env`配置 NACOS_ACCESS_KEY',
'secretKey.required' => '请填写`.env`配置 NACOS_SECRET_KEY',
'endpoint.required' => '请填写`.env`配置 NACOS_ENDPOINT',
'namespace.required' => '请填写`.env`配置 NACOS_NAMESPACE',
'dataId.required' => '请填写`.env`配置 NACOS_DATA_ID',
'group.required' => '请填写`.env`配置 NACOS_GROUP',
];
$validator = Validator::make($data, $rules, $messages);
if ($validator->fails()) {
foreach ($validator->getMessageBag()->toArray() as $item) {
foreach ($item as $value) {
$this->error($value);
}
}
return false;
}
return true;
}
/**
* 获取配置
*
* Date: 2020/6/10
* @return bool
*/
private function getConfig()
{
$acmHost = str_replace(['host', 'port'], [$this->getServer(), $this->port],
'http://host:port/diamond-server/config.co');
$query = [
'dataId' => urlencode($this->dataId),
'group' => urlencode($this->group),
'tenant' => urlencode($this->namespace),
];
$headers = $this->getHeaders();
$response = $this->client->get($acmHost, [
'headers' => $headers,
'query' => $query,
]);
if ($response->getReasonPhrase() == 'OK') {
return $response->getBody()->getContents();
} else {
return false;
}
}
/**
* 发布配置
*
* Date: 2020/6/10
* @return bool
*/
public function publishConfig()
{
$acmHost = str_replace(
['host', 'port'],
[$this->getServer(), $this->port],
'http://host:port/diamond-server/basestone.do?method=syncUpdateAll');
$headers = $this->getHeaders();
$formParams = [
'dataId' => urlencode($this->dataId),
'group' => urlencode($this->group),
'tenant' => urlencode($this->namespace),
'content' => file_get_contents('.env'),
];
$response = $this->client->post($acmHost, [
'headers' => $headers,
'form_params' => $formParams,
]);
$result = json_decode($response->getBody()->getContents(), 1);
return $result['message'] == 'OK';
}
public function removeConfig()
{
$acmHost = str_replace(['host', 'port'], [$this->getServer(), $this->port],
'http://host:port/diamond-server//datum.do?method=deleteAllDatums');
$headers = $this->getHeaders();
$formParams = [
'dataId' => urlencode($this->dataId),
'group' => urlencode($this->group),
'tenant' => urlencode($this->namespace),
];
$response = $this->client->post($acmHost, [
'headers' => $headers,
'form_params' => $formParams,
]);
$result = json_decode($response->getBody()->getContents(), 1);
return $result['message'] == 'OK';
}
/**
* 获取配置服务器地址
*
* Date: 2020/6/10
* @return string
*/
private function getServer()
{
if ($this->serverUrl) {
return $this->serverUrl;
}
$serverHost = str_replace(
['host', 'port'],
[$this->endpoint, $this->port],
'http://host:port/diamond-server/diamond');
$response = $this->client->get($serverHost);
return $this->serverUrl = rtrim($response->getBody()->getContents(), PHP_EOL);
}
/**
* 获取请求头
*
* Date: 2020/6/10
* @return array
*/
private function getHeaders()
{
$headers = [
'Diamond-Client-AppName' => 'ACM-SDK-PHP',
'Client-Version' => '0.0.1',
'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
'exConfigInfo' => 'true',
'Spas-AccessKey' => $this->accessKey,
'timeStamp' => round(microtime(true) * 1000),
];
$headers['Spas-Signature'] = $this->getSign($headers['timeStamp']);
return $headers;
}
/**
* 获取签名
*
* @param $timeStamp
* Date: 2020/6/10
* @return string
*/
private function getSign($timeStamp)
{
$signStr = $this->namespace.'+';
if (is_string($this->group)) {
$signStr .= $this->group."+";
}
$signStr = $signStr.$timeStamp;
return base64_encode(hash_hmac(
'sha1',
$signStr,
$this->secretKey,
true
));
}
}
使用示例
- 注册账号,开通服务这些就不说了
.env
添加配置项NACOS_ACCESS_KEY
NACOS_SECRET_KEY
等- php artisan nacos 0 获取配置
- php artisan nacos 1 发布配置
- php artisan nacos 2 删除配置
配置项说明
NACOS_ENDPOINT= #nacos节点 如使用阿里云服务 即:acm.aliyun.com
NACOS_DATA_ID= #项目ID 可以填项目名
NACOS_GROUP= #分组ID 这里可以用于区分环境 建议 local production test 等值
NACOS_NAMESPACE= # 命名空间 建议用来区分服务器 server-A server-B
NACOS_ACCESS_KEY= #阿里云access_key 建议使用子账号access_key
NACOS_SECRET_KEY= #阿里云secret_key 建议使用子账号secret_key
总结
使用 nacos 后,再也不用担心 .env.example
忘记加配置项,共享配置也不是件麻烦事了,自动部署也不需要频繁的改动配置了。
本作品采用《CC 协议》,转载必须注明作者和本文链接
看不明白
其实没有觉得特别方便,现在laravel都可以完全docker开发,版本不是问题,
本地开发环境参数都是不同的container的,无所谓分享。
这不就是配置中心…硬塞就是硬塞
就目前单体应用,我推荐Envault
能作为注册中心吗?
请教:那为什么不用阿里云的ACM呢?
nacos是微服务管理用的,laravel是运行在fpm模式的,可能不太合适,如果用在cli模式下比较好,现在已经有其他框架实现了。感觉扯得有点远了。你这个其实写的很好的,只是用laravel的大多数用不到,另外建议可以封装成插件。
搞php的,应该是不会用到nacos的。.env没有什么不好的,多一个依赖就多一个攻击面,Nacos比较容易被黑,而且还多了一块运维的工作