上篇中,我们主要介绍了使用docker-compose对Windows Docker单服务器进行远程管理,编译和部署镜像,并且设置容器的自动启动。但是,还有一些重要的问题没有解决,这些问题不解决,就完全谈不上运维: 问题一:如此部署的应用,在宿主机外部,只能通过宿主机的ip加一个个特定的端口来访问每个容器内的应用,这显然是不满足实际需求的。 问题二:相比于将应用直接部署在有UI界面的Windows Server,因为每个应用部署于自己的Windows Docker容器,当应用运行时发生各种问题时,比如,cpu高,内存高,访问变慢等等,如何才能方便地排查问题呢?即使你愿意一个个容器attach上去,也因为它没有UI,远没有传统有UI界面的Windows Server上容易。所以,我们必须有必要的工具来更方便的监控容器的运行。 下篇
负载均衡和反向代理要解决问题一:
通常,支持反向代理的组件,往往也同时提供负载均衡功能。例如:F5、nginx、Apache2、HAProxy甚至IIS的ARR等。不同的方案可能侧重点略有不同,我们可以根据实际情况选择不同的方案。另外,既然我们的应用部署在Windows Docker服务器,那么最好我们所用的代理组件同样能部署在Windows Docker容器,这样我们就能用一致的流程和工具来管理。下面的示例中,我们选择Apache2,实现一个部署于Windows Docker部署的反向代理加负载均衡器。 为了演示负载均衡,我们新建一个two-instances-demo目录,其中docker-compose.yml里为iis-demo添加两个不同内部ip的容器实例,再添加一个apache容器,它的Dockerfile定义在apache目录中。 version: '2.1'services: apache: build: . image: 'apache-proxy:1.0' ports: - '80:80' networks: nat: iis-demo-1: build: ../ image: 'iis-demo:1.0' ports: - '80' networks: nat: ipv4_address: 172.24.128.101 volumes: - 'c:/temp:c:/inetpub/logs/LogFiles' environment: - 'env1=LIVE1' - 'env2=LIVE2' - 'HOSTS=1.2.3.4:TEST.COM' iis-demo-2: build: ../ image: 'iis-demo:1.0' ports: - '80' networks: nat: ipv4_address: 172.24.128.102 volumes: - 'c:/temp:c:/inetpub/logs/LogFiles' environment: - 'env1=LIVE1' - 'env2=LIVE2' - 'HOSTS=1.2.3.4:TEST.COM'networks: nat: external: true Apache的Dockerfile,很简单,只是安装和覆盖默认conf,然后,运行https.exe服务。
然后,我们打开一个命令窗口,在two-instances-demo目录下执行docker-compose up,稍作等待,等容器运行起来,然后,在宿主机外部,注意一定是从宿主机外部(原因上一篇文章有解释),访问宿主机的ip地址下的/iis-demo/路径,可以看到,iis-demo的默认页面内容能够被正常显示,说明反向代理和负载均衡已经正常运行了。 其他备注:
日志解析和监控要解决问题二:
对于容器的运行时的性能指标,docker的命令行工具,提供了docker stats命令,可以查看每个容器实时的CPU、内存、IO能指标,我们可以考虑定时将它们收集保存起来,用于集中化的监控。 另外,玩过Linux下docker的小伙伴们肯定知道,docker会将每个容器内运行时打印到console的内容,都记录在宿主机的docker日志目录中,而大多数Linux容器部署应用,大多会将应用自己的日志也打印到console,这样,所有的日志都可以包含在docker宿主机的容器日志中。这有什么好处呢?好处就是,我们可以在宿主机上,配置日志解析工具,比如Logstash或fluentd,解析和forward所有日志。 在Windows Docker下,由于Windows的基因问题,一方面,大多数应用都是基于IIS的应用,没办法将日志直接打印到console,另一方面,IIS本身的日志和Windows的EventLog也无法方便地配置把它们打印到console,所以,一般的做法是,需要把这些日志所在的目录,mount到宿主机,然后,再在宿主机上统一解析。特别对于Windows EventLog,它在Windows文件系统的格式无法被简单读取和解析,因此,我们一般需要用到一些地第三方工具,如nxlog和Elastic公司Beats(这两个工具都是免费开源的)将解析后的Windows EventLog保存为易于解析的格式,比如JSON格式。 对于经过解析的日志,现在比较流行的做法是把它导入Elasticsearch,这样就可以方便通过kibana,grafana这样的工具,可视化查看,远程实时监控了。 本想将相关组件都做成Windows Docker镜像,方便大家能直接下载运行的,无奈这些组件都比较大,动辄几十上百兆,国内的网络下,不FQ的情况下,我本机下载都很费劲,想必,做成Docker镜像,大家运行的体验也不会很好,所以,就先不做了。下面简单介绍一下这几个工具的使用,并分享一些核心的配置脚本,给大家做个参考。 首先是对IIS Log和Windows EventLog的解析,以nxlog为例: nxlog的Windows版安装完之后,是一个Windows Service。它的配置文件在C:\Program Files (x86)\nxlog\conf目录下,每次更改nf文件,都需要重启nxlog service使配置生效。最经常的用法,一般是将原始的IIS的W3C格式的日志,还有Windows EventLog解析为JSON格式,然后经过Logstash中转之后保存到Elasticsearch。 下面是一个典型的解析IIS W3C格式Log的nf文件: define ROOT C:\Program Files (x86)\nxlogModuledir %ROOT%\modulesCacheDir %ROOT%\dataPidfile %ROOT%\data\nxlog.pidSpoolDir %ROOT%\dataLogFile %ROOT%\data\nxlog.log<Extension json> Module xm_json</Extension><Extension w3c> Module xm_csv Fields $log_date, $log_time, $log_site_id, $log_server_name, $log_server_ip, $log_http_method, $log_path, $log_query, $log_port, $log_user_name, $log_client_ip, $log_http_version, $log_user_agent, $log_referer, $log_domain_name, $log_http_status, $log_http_substatus, $log_win32_status, $log_response_size, $log_request_size, $log_time_taken FieldTypes string, string, string, string, string, string, string, string, integer, string, string, string, string, string, string, integer, integer, integer, integer, integer, integer Delimiter ' ' EscapeControl FALSE UndefValue -</Extension><Input in-iis> Module im_file File 'C:\\temp\\iislogs\\\\u_ex*.log' SavePos True ReadFromLast False ActiveFiles 10 Exec if $raw_event =~ /^#/ drop(); else { w3c->parse_csv(); if ($log_date) $log_request_timestamp = $log_date + ' ' + $log_time; else drop(); if ($log_referer) $log_referer = lc($log_referer); if ($log_path) { $log_path = lc($log_path); if $log_path =~ /(\.[^.]+)/ $log_request_type = $1; else $log_request_type = 'unknown'; } if ($log_user_agent) $log_user_agent = replace($log_user_agent, '+', ' '); if ($log_domain_name) $log_domain_name = replace($log_domain_name, ':80', ''); };</Input><Output iis> Module om_tcp Exec $raw_event = to_json(); Host localhost Port 5151</Output><Route out_iis> Path in-iis => iis</Route> 它的第一部分Extension W3C定义了哪些W3C字段需要解析;第二部分iis input调用w3c扩展组件,解析指定目录的日志文件,做必要的规整;第三部分定义了如何保存解析结果,将解析后的消息,保存为JSON格式的键值对,然后写入一个Logstash的TCP输入端口。 下面是一个典型的nxlog解析Windows EventLog的例子:
这里,第一部分我们定义了如何从Windows EventLog中筛选消息;第二部分,定义了如何以每20M为大小,分割保存最新的EventLog为JSON。 将JSON格式的数据通过Logstash保存到Elasticsearch非常简单,网上示例比比皆是,这理解不举例了。 最后分享一个Logstash的配置文件,用于每隔30秒,收集宿主机上所有docker容器的性能指标,并且以JSON格式,保存到Elasticsearch: input { exec { command => 'C:\temp\get-docker-stats.cmd' interval => 30 codec => line {} }}filter { grok { match => { 'message' => '%{WORD:container_id} %{WORD:container_name} %{NUMBER:cpu_percent}% %{NUMBER:mem} %{WORD:mem_unit} %{NUMBER:net_in} %{WORD:net_in_unit} / %{NUMBER:net_out} %{WORD:net_out_unit} %{NUMBER:block_in} %{WORD:block_in_unit} / %{NUMBER:block_out} %{WORD:block_out_unit}' } } mutate { convert => { 'cpu_percent' => 'float' 'mem' => 'float' 'net_in' => 'float' 'net_out' => 'float' 'block_in' => 'float' 'block_out' => 'float' } } #calc memory bytes if [mem_unit] == 'KiB' { ruby { code => 'event.set('mem_bytes',event.get('mem').to_f*1024)' } } if [mem_unit] == 'MiB' { ruby { code => 'event.set('mem_bytes',event.get('mem').to_f*1024*1024)' } } if [mem_unit] == 'GiB' { ruby { code => 'event.set('mem_bytes',event.get('mem').to_f*1024*1024*1024)' } } #calc net_in bytes if [net_in_unit] == 'kB' { ruby { code => 'event.set('net_in_bytes',event.get('net_in').to_f*1000)' } } if [net_in_unit] == 'MB' { ruby { code => 'event.set('net_in_bytes',event.get('net_in').to_f*1000*1000)' } } #calc net_out bytes if [net_out_unit] == 'kB' { ruby { code => 'event.set('net_out_bytes',event.get('net_out').to_f*1000)' } } if [net_out_unit] == 'MB' { ruby { code => 'event.set('net_out_bytes',event.get('net_out').to_f*1000*1000)' } } #calc block_in bytes if [block_in_unit] == 'kB' { ruby { code => 'event.set('block_in_bytes',event.get('block_in').to_f*1000)' } } if [block_in_unit] == 'MB' { ruby { code => 'event.set('block_in_bytes',event.get('block_in').to_f*1000*1000)' } } #calc block_out bytes if [block_out_unit] == 'kB' { ruby { code => 'event.set('block_out_bytes',event.get('block_out').to_f*1000)' } } if [block_out_unit] == 'MB' { ruby { code => 'event.set('block_out_bytes',event.get('block_out').to_f*1000*1000)' } } mutate { remove_field => ['mem', 'mem_unit', 'net_in', 'net_in_unit', 'net_out', 'net_out_unit', 'block_in', 'block_in_unit', 'block_out', 'block_out_unit', 'message', 'command'] }}output { elasticsearch { hosts => ['localhost'] index => 'logstash-docker-stats-log-%{+YYYY.MM.dd}' timeout => 30 workers => 1 }} 其中,get-docker-stats.cmd文件真正执行docker stats命令,获取所有正在运行的容器的性能指标,其中具体的命令如下:
所有JSON格式的监控数据保存到Elasticsearch以后,使用kibana或者grafana进行数据的展示、设置监控警报等等,就相对比较简单了,目前也非常流行,网上应该是能找到非常多的示例的,使用上也不存在Linux和Windows的区别,这里就不详述了。对这方面有疑问的同学,我们可以私下交流。 单节点Windows Docker服务器简单运维下篇完。 我们简单回顾一下,在最近的上下两篇中,我们介绍了运维一个单节点Windows Docker服务器的主要思路和常用工具。这些思想和工具,也是更复杂的docker集群模式下的运维的基础。运维的水很深,Windows Docker的运维,对大多数公司来说,也都还只是在摸索的过程中。文中的示例,更多的还在于抛砖引用,大家不要受其局限,要习惯于发挥想象力,创造性的解决问题,提出新的思路。 前面的示例中,虽然尽可能不依赖于Linux下的容器,但毕竟Linux下的容器和各种支持工具,现在已经非常成熟了,在实际的部署中,还是应该 |
|