XNginx - nginx 集羣可視化管理工具

以前團隊的nginx管理,都是運維同窗每次去修改配置文件,而後重啓,很是不方便,一直想找一個能夠方便管理nginx集羣的工具,翻遍web,未尋到可用之物,因而本身設計開發了一個。java

效果預覽node

若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。nginx

  1. 集羣group管理界面

 

XNginx - nginx 集羣可視化管理工具

 

 

能夠管理group的節點,配置文件,修改後能夠一鍵重啓全部節點,且配置文件出錯時會提示錯誤,不會影響線上服務。web

2.集羣Node節點管理spring

XNginx - nginx 集羣可視化管理工具

 

 

3 .集羣Node節點日誌查看服務器

XNginx - nginx 集羣可視化管理工具

 

 

  1. 生成的配置文件預覽

 

XNginx - nginx 集羣可視化管理工具

 

 

  1. vhost管理

 

XNginx - nginx 集羣可視化管理工具

 

 

設計思路數據結構

數據結構:架構

一個nginxGroup,擁有多個NginxNode,共享同一份配置文件。app

分佈式架構: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來方便調用。

若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。

定義接口:

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("
"));
 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()), "
"));
 }
 }
 }
 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);
 }

歡迎工做一到八年的Java工程師朋友們加入Java高級交流:854630135

本羣提供免費的學習指導 架構資料 以及免費的解答

不懂得問題均可以在本羣提出來 以後還會有直播平臺和講師直接交流噢

相關文章
相關標籤/搜索