以前團隊的nginx管理,都是運維同窗每次去修改配置文件,而後重啓,很是不方便,一直想找一個能夠方便管理nginx集羣的工具,翻遍web,未尋到可用之物,因而本身設計開發了一個。java
效果預覽node
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:854630135,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。nginx
能夠管理group的節點,配置文件,修改後能夠一鍵重啓全部節點,且配置文件出錯時會提示錯誤,不會影響線上服務。web
2.集羣Node節點管理spring
3 .集羣Node節點日誌查看服務器
設計思路數據結構
數據結構:架構
一個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
本羣提供免費的學習指導 架構資料 以及免費的解答
不懂得問題均可以在本羣提出來 以後還會有直播平臺和講師直接交流噢