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

之前團(tuán)隊的nginx管理,都是運(yùn)維同學(xué)每次去修改配置文件,然后重啟,非常不方便,一直想找一個可以方便管理nginx集群的工具,翻遍web,未尋到可用之物,于是自己設(shè)計開發(fā)了一個。

效果預(yù)覽

如果想學(xué)習(xí)Java工程化、高性能及分布式、深入淺出。微服務(wù)、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。

集群group管理界面

可以管理group的節(jié)點(diǎn),配置文件,修改后可以一鍵重啟所有節(jié)點(diǎn),且配置文件出錯時會提示錯誤,不會影響線上服務(wù)。

2.集群Node節(jié)點(diǎn)管理

3 .集群Node節(jié)點(diǎn)日志查看

生成的配置文件預(yù)覽

vhost管理

設(shè)計思路

數(shù)據(jù)結(jié)構(gòu):

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

分布式架構(gòu):Manager節(jié)點(diǎn)+agent節(jié)點(diǎn)+web管理

每個nginx機(jī)器部署一個agent,agent啟動后自動注冊到manager,通過web可以設(shè)置agent所屬group,以及管理group的配置文件。

配置文件變更后,manager生成配置文件,分發(fā)給存活的agent,檢驗OK后,控制agent重啟nginx。

關(guān)鍵技術(shù)點(diǎn)

分布式管理

一般分布式可以借助zookeeper等注冊中心來實現(xiàn),作為java項目,其實使用EurekaServer就可以了:

manager加入eureka依賴:

org.springframework.cloud

spring-cloud-starter

org.springframework.cloud

spring-cloud-starter-netflix-eureka-server

然后在入口程序添加 @EnableEurekaServer

agent 添加注冊配置:

eureka:

instance:

prefer-ip-address: true

client:

service-url:

defaultZone: http://admin:admin@ip:3002/eureka/

manager 節(jié)點(diǎn)獲取存活的agent,可以通過EurekaServerContextHolder來獲取注冊的agent,同時可以通過定時任務(wù)自動發(fā)現(xiàn)新節(jié)點(diǎn)。

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 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 getAliveAgents() {

List instances = new ArrayList<>();

List sortedApplications = getRegistry().getSortedApplications();

Optional 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調(diào)用

manager 需要控制agent,按最簡單的方案,agent提供rest服務(wù),從Eureka獲取地址后直接調(diào)用就可以了,另外可以借助feign來方便調(diào)用。

如果想學(xué)習(xí)Java工程化、高性能及分布式、深入淺出。微服務(wù)、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。

定義接口:

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 getAccesslogs(@Param("lines") int lines);

@RequestLine("GET /nginx/errorlogs/{lines}")

List getErrorLogs(@Param("lines") int lines);

}

agent 實現(xiàn)功能:

@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 getAccesslogs(@PathVariable Integer lines) {

return nginxControl.getAccessLogs(lines);

}

}

manager 調(diào)用;

先生成一個Proxy實例,其中nodeurl是agent節(jié)點(diǎn)的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);

}

然后調(diào)用就簡單了,比如要啟動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 action){

List alivedNodes = nodeDiscover.getAliveAgents();

if(alivedNodes.size() == 0){

throw new XNginxException("no alived agent!");

}

List 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 locations;

private String root;

private String index;

private String access_log;

}

其中核心的LocationDirective,設(shè)計思路是passAddress存儲location的目標(biāo)地址,可以是url,也可以是upstream,通過type來區(qū)分,同時如果有upstream,則通過proxy來設(shè)置負(fù)載信息。

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 rewrites;

private String advanced;

private String passAddress;

}

再來看ProxyDirective,通過balance來區(qū)分是普通的url還是upstream,如果是upstream,servers存儲負(fù)載的服務(wù)器。

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 servers;

}

歷史數(shù)據(jù)導(dǎo)入

已經(jīng)有了配置信息,可以通過解析導(dǎo)入系統(tǒng),解析就是常規(guī)的文本解析,這里不再贅述。

核心思想就是通過匹配大括號,將配置文件分成block,然后通過正則等提取信息,比如下面的代碼拆分出server{...}

private List blocks() {

List blocks = new ArrayList<>();

List 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 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 hostDirectiveList, List proxyDirectiveList) {

Map 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

本群提供免費(fèi)的學(xué)習(xí)指導(dǎo) 架構(gòu)資料 以及免費(fèi)的解答

不懂得問題都可以在本群提出來 之后還會有直播平臺和講師直接交流噢

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1.情緒管理還行。 2.陪伴:今天來客人了,媽媽老家同學(xué)徐州過來,今天陪阿姨和妹妹一起長江邊抓螃蟹。
    azhifeng閱讀 188評論 0 0
  • 我是一個不太會主動聯(lián)系人的人。 加了好友我不會去主動跟人聊,因為我不知道該怎么開頭。如果給別人發(fā)消息,別人不回我會...
    冬忍藤閱讀 125評論 0 0
  • 01 其實我很喜歡坐夜晚的火車,汽車以及公交。 疲憊的靠在椅背上的女孩,行色匆匆的趕路人,飛逝而過的星星點(diǎn)點(diǎn)亮起的...
    云打傘閱讀 321評論 2 0
  • 一、開閉原則 1. 定義: 一個軟件實體應(yīng)當(dāng)對擴(kuò)展開放,對修改關(guān)閉。在設(shè)計一個模塊時,應(yīng)當(dāng)使這個模塊可以在不被修改...
    CrixalisAs閱讀 604評論 0 0