XNginx - nginx 集群可視化管理工具
之前團隊的nginx管理,都是運維同學每次去修改配置文件,然後重啟,非常不方便,一直想找一個可以方便管理nginx集群的工具,翻遍web,未尋到可用之物,於是自己設計開發了一個。
效果預覽
- 集群group管理界面
可以管理group的節點,配置文件,修改後可以一鍵重啟所有節點,且配置文件出錯時會提示錯誤,不會影響線上服務。
2.集群Node節點管理
3 .集群Node節點日誌查看
- 生成的配置文件預覽
- vhost管理
設計思路
數據結構:
一個nginxGroup,擁有多個NginxNode,共享同一份配置文件。
分布式架構:Manager節點+agent節點+web管理
每個nginx機器部署一個agent,agent啟動後自動註冊到manager,通過web可以設置agent所屬group,以及管理group的配置文件。
配置文件變更後,manager生成配置文件,分發給存活的agent,檢驗OK後,控制agent重啟nginx。
關鍵技術點
分布式管理
一般分布式可以借助zookeeper等註冊中心來實現,作為java項目,其實使用EurekaServer就可以了:
manager加入eureka依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
然後在入口程序添加 @EnableEurekaServer
agent 添加註冊配置:
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://admin:admin@ip:3002/eureka/
manager 節點獲取存活的agent,可以通過EurekaServerContextHolder來獲取註冊的agent,同時可以通過定時任務自動發現新節點。
public class NginxNodeDiscover { private static final String AGENT_NAME = "XNGINXAGENT"; private PeerAwareInstanceRegistry getRegistry() { return getServerContext().getRegistry(); } private EurekaServerContext getServerContext() { return EurekaServerContextHolder.getInstance().getServerContext(); } @Autowired NginxNodeRepository nginxNodeRepository; @Scheduled(fixedRate = 60000) public void discoverNginxNode() { List<String> nodes = getAliveAgents(); nodes.stream().forEach(node->{ if(!nginxNodeRepository.findByAgent(node).isPresent()){ NginxNode nginxNode = new NginxNode(); nginxNode.setAgent(node); nginxNode.setName(node); nginxNodeRepository.save(nginxNode); } }); } public List<String> getAliveAgents() { List<String> instances = new ArrayList<>(); List<Application> sortedApplications = getRegistry().getSortedApplications(); Optional<Application> targetApp = sortedApplications.stream().filter(a->a.getName().equals(AGENT_NAME)).findFirst(); if(targetApp.isPresent()){ Application app = targetApp.get(); for (InstanceInfo info : app.getInstances()) { instances.add(info.getHomePageUrl()); } } return instances; } }
RPC調用
manager 需要控制agent,按最簡單的方案,agent提供rest服務,從Eureka獲取地址後直接調用就可以了,另外可以借助feign來方便調用。
定義接口:
public interface NginxAgentManager {
@RequestLine("GET /nginx/start")
RuntimeBuilder.RuntimeResult start() ;
@RequestLine("GET /nginx/status")
RuntimeBuilder.RuntimeResult status() ;
@RequestLine("GET /nginx/reload")
RuntimeBuilder.RuntimeResult reload() ;
@RequestLine("GET /nginx/stop")
RuntimeBuilder.RuntimeResult stop();
@RequestLine("GET /nginx/testConfiguration")
RuntimeBuilder.RuntimeResult testConfiguration();
@RequestLine("GET /nginx/kill")
RuntimeBuilder.RuntimeResult kill() ;
@RequestLine("GET /nginx/restart")
RuntimeBuilder.RuntimeResult restart() ;
@RequestLine("GET /nginx/info")
NginxInfo info();
@RequestLine("GET /nginx/os")
OperationalSystemInfo os() ;
@RequestLine("GET /nginx/accesslogs/{lines}")
List<NginxLoggerVM> getAccesslogs(@Param("lines") int lines);
@RequestLine("GET /nginx/errorlogs/{lines}")
List<NginxLoggerVM> getErrorLogs(@Param("lines") int lines);
}
agent 實現功能:
@RestController
@RequestMapping("/nginx")
public class NginxResource {
...
@PostMapping("/update")
@Timed
public String update(@RequestBody NginxConf conf){
if(conf.getSslDirectives()!=null){
for(SslDirective sslDirective : conf.getSslDirectives()){
nginxControl.conf(sslDirective.getCommonName(),sslDirective.getContent());
}
}
return updateConfig(conf.getConf());
}
@GetMapping("/accesslogs/{lines}")
@Timed
public List<NginxLoggerVM> getAccesslogs(@PathVariable Integer lines) {
return nginxControl.getAccessLogs(lines);
}
}
manager 調用;
先生成一個Proxy實例,其中nodeurl是agent節點的url地址
public NginxAgentManager getAgentManager(String nodeUrl){
return Feign.builder()
.options(new Request.Options(1000, 3500))
.retryer(new Retryer.Default(5000, 5000, 3))
.requestInterceptor(new HeaderRequestInterceptor())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(NginxAgentManager.class, nodeUrl);
}
然後調用就簡單了,比如要啟動group:
public void start(String groupId){
operateGroup(groupId,((conf, node) -> {
NginxAgentManager manager = getAgentManager(node.getAgent());
String result = manager.update(conf);
if(!result.equals("success")){
throw new XNginxException("node "+ node.getAgent()+" update config file failed!");
}
RuntimeBuilder.RuntimeResult runtimeResult = manager.start();
if(!runtimeResult.isSuccess()){
throw new XNginxException("node "+ node.getAgent()+" start failed,"+runtimeResult.getOutput());
}
}));
}
public void operateGroup(String groupId,BiConsumer<NginxConf,NginxNode> action){
List<String> alivedNodes = nodeDiscover.getAliveAgents();
if(alivedNodes.size() == 0){
throw new XNginxException("no alived agent!");
}
List<NginxNode> nginxNodes = nodeRepository.findAllByGroupId(groupId);
if(nginxNodes.size() ==0){
throw new XNginxException("the group has no nginx Nodes!");
}
NginxConf conf = nginxConfigService.genConfig(groupId);
for(NginxNode node : nginxNodes){
if(!alivedNodes.contains(node.getAgent())){
continue;
}
action.accept(conf, node);
}
}
Nginx 配置管理
nginx的核心是各種Directive(指令),最核心的是vhost和Location。
我們先來定義VHOST:
public class VirtualHostDirective implements Directive {
private Integer port = 80;
private String aliases;
private boolean enableSSL;
private SslDirective sslCertificate;
private SslDirective sslCertificateKey;
private List<LocationDirective> locations;
private String root;
private String index;
private String access_log;
}
其中核心的LocationDirective,設計思路是passAddress存儲location的目標地址,可以是url,也可以是upstream,通過type來區分,同時如果有upstream,則通過proxy來設置負載信息。
public class LocationDirective {
public static final String PROXY = "PROXY";
public static final String UWSGI = "UWSGI";
public static final String FASTCGI = "FASTCGI";
public static final String COMMON = "STATIC";
private String path;
private String type = COMMON;
private ProxyDirective proxy;
private List<String> rewrites;
private String advanced;
private String passAddress;
}
再來看ProxyDirective,通過balance來區分是普通的url還是upstream,如果是upstream,servers存儲負載的服務器。
public class ProxyDirective implements Directive {
public static final String BALANCE_UPSTREAM = "upstream";
public static final String BALANCE_URL = "url";
private String name;
private String strategy;
/**
* Upstream balance type : upsteam,url
*/
private String balance = BALANCE_UPSTREAM;
private List<UpstreamDirectiveServer> servers;
}
歷史數據導入
已經有了配置信息,可以通過解析導入系統,解析就是常規的文本解析,這裏不再贅述。
核心思想就是通過匹配大括號,將配置文件分成block,然後通過正則等提取信息,比如下面的代碼拆分出server{...}
private List<String> blocks() {
List<String> blocks = new ArrayList<>();
List<String> lines = Arrays.asList(fileContent.split("\n"));
AtomicInteger atomicInteger = new AtomicInteger(0);
AtomicInteger currentLine = new AtomicInteger(1);
Integer indexStart = 0;
Integer serverStartIndex = 0;
for (String line : lines) {
if (line.contains("{")) {
atomicInteger.getAndIncrement();
if (line.contains("server")) {
indexStart = currentLine.get() - 1;
serverStartIndex = atomicInteger.get() - 1;
}
} else if (line.contains("}")) {
atomicInteger.getAndDecrement();
if (atomicInteger.get() == serverStartIndex) {
if (lines.get(indexStart).trim().startsWith("server")) {
blocks.add(StringUtils.join(lines.subList(indexStart, currentLine.get()), "\n"));
}
}
}
currentLine.getAndIncrement();
}
return blocks;
}
配置文件生成
配置文件生成,一般是通過模板引擎,這裏也不例外,使用了Velocity庫。
public static StringWriter mergeFileTemplate(String pTemplatePath, Map<String, Object> pDto) {
if (StringUtils.isEmpty(pTemplatePath)) {
throw new NullPointerException("????????????");
}
StringWriter writer = new StringWriter();
Template template;
try {
template = ve.getTemplate(pTemplatePath);
} catch (Exception e) {
throw new RuntimeException("????????", e);
}
VelocityContext context = VelocityHelper.convertDto2VelocityContext(pDto);
try {
template.merge(context, writer);
} catch (Exception e) {
throw new RuntimeException("????????", e);
}
return writer;
}
定義模板:
#if(${config.user})user ${config.user};#end
#if(${config.workerProcesses}== 0 )
worker_processes auto;
#else
worker_processes ${config.workerProcesses};
#end
pid /opt/xnginx/settings/nginx.pid;
events {
multi_accept off;
worker_connections ${config.workerConnections};
}
...
生成配置文件;
public static StringWriter buildNginxConfString(ServerConfig serverConfig, List<VirtualHostDirective> hostDirectiveList, List<ProxyDirective> proxyDirectiveList) {
Map<String,Object> map = new HashMap<>();
map.put("config",serverConfig);
map.put("upstreams", proxyDirectiveList);
map.put("hosts",hostDirectiveList);
return VelocityHelper.mergeFileTemplate(NGINX_CONF_VM, map);
}
管理web
管理web基於ng-alain框架,typescript+angular mvvm開發起來,和後端沒有本質區別
開發相對簡單,這裏不贅述。
小結
目前只實現了基本的管理功能,後續可根據需要再繼續補充完善,比如支持業務、負責人等信息管理維護。
作者:Jadepeng
出處:jqpeng的技術記事本--http://www.cnblogs.com/xiaoqi
您的支持是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
XNginx - nginx 集群可視化管理工具