上篇中,我們主要介紹了使用docker-compose對Windows Docker單服務(wù)器進行遠程管理,編譯和部署鏡像,并且設(shè)置容器的自動啟動。但是,還有一些重要的問題沒有解決,這些問題不解決,就完全談不上運維:
問題一:如此部署的應(yīng)用,在宿主機外部,只能通過宿主機的ip加一個個特定的端口來訪問每個容器內(nèi)的應(yīng)用,這顯然是不滿足實際需求的。
問題二:相比于將應(yīng)用直接部署在有UI界面的Windows Server,因為每個應(yīng)用部署于自己的Windows Docker容器,當應(yīng)用運行時發(fā)生各種問題時,比如,cpu高,內(nèi)存高,訪問變慢等等,如何才能方便地排查問題呢?即使你愿意一個個容器attach上去,也因為它沒有UI,遠沒有傳統(tǒng)有UI界面的Windows Server上容易。所以,我們必須有必要的工具來更方便的監(jiān)控容器的運行。
下篇
負載均衡和反向代理
日志解析和監(jiān)控
要解決問題一:
首先,我們需要一個機制,當用戶訪問我們的ip或域名時,服務(wù)器能根據(jù)不同的域名,或者不同的子路徑,在相同的端口比如80或者443,從每個應(yīng)用容器返回內(nèi)容——這就是反向代理;
接著,我們不希望我們的應(yīng)用在更新、系統(tǒng)維護、或者局部故障時無法提供服務(wù),所以,每個部署的應(yīng)用不能是單點,那么,如果同一個應(yīng)用部署了多個容器實例,如何才能讓他們Serve相同的域名和URL請求呢?——這就是負載均衡;
通常,支持反向代理的組件,往往也同時提供負載均衡功能。例如:F5、nginx、Apache2、HAProxy甚至IIS的ARR等。不同的方案可能側(cè)重點略有不同,我們可以根據(jù)實際情況選擇不同的方案。另外,既然我們的應(yīng)用部署在Windows Docker服務(wù)器,那么最好我們所用的代理組件同樣能部署在Windows Docker容器,這樣我們就能用一致的流程和工具來管理。下面的示例中,我們選擇Apache2,實現(xiàn)一個部署于Windows Docker部署的反向代理加負載均衡器。
為了演示負載均衡,我們新建一個two-instances-demo目錄,其中docker-compose.yml里為iis-demo添加兩個不同內(nèi)部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服務(wù)。
FROM microsoft/windowsservercore:latestADD vc_redist.x64.exe /vc_redist.x64.exeRUN /vc_redist.x64.exe /qADD httpd-2.4.25-x64-vc14-r1.zip /RUN powershell -executionpolicy bypass -Command 'expand-archive -Path 'c:\httpd-2.4.25-x64-vc14-r1.zip' -DestinationPath 'c:\''COPY conf /confRUN del c:\Apache24\conf\httpd.confRUN del c:\Apache24\conf\extra\httpd-vhosts.confRUN copy 'c:\conf\httpd.conf' 'c:\Apache24\conf\'RUN copy 'c:\conf\extra\httpd-vhosts.conf' 'c:\Apache24\conf\extra\'ENTRYPOINT ['C:\\Apache24\\bin\\httpd.exe']
然后,我們打開一個命令窗口,在two-instances-demo目錄下執(zhí)行docker-compose up,稍作等待,等容器運行起來,然后,在宿主機外部,注意一定是從宿主機外部(原因上一篇文章有解釋),訪問宿主機的ip地址下的/iis-demo/路徑,可以看到,iis-demo的默認頁面內(nèi)容能夠被正常顯示,說明反向代理和負載均衡已經(jīng)正常運行了。
其他備注:
這里的Apache配置是出于演示目的,拋磚引玉,極度簡化的版本,如果用于真實環(huán)境,請根據(jù)Apache官方文檔以和相應(yīng)的性能測試,做必要調(diào)整;
除了Apache之外,nginx可以運行于Windows,但是性能不佳,官方不推薦在Windows下使用;
HAProxy只能運行于Linux;
IIS的ARR,反向代理功能尚可,但是負載均衡依賴于IIS的集群,限制頗多,如果同一個應(yīng)用的容器只需要部署單個實例的話可以考慮;
這里在docker-compose.yml中定義同一個應(yīng)用的兩個服務(wù)的做法,只是在不使用docker的集群化部署時的簡化做法,如果應(yīng)用了Swarm集群,或者使用了其他支持集群和自動擴展的容器編排方案,是可以直接通過docker-compose的scale參數(shù),讓一個docker-compose服務(wù)運行多個實例的,這個我們以后會聊到;
要解決問題二:
首先,我們需要一個方便的機制查看宿主機上每個容器運行時的CPU,內(nèi)存,IO等資源開銷,還要能保留這些運行情況的歷史紀錄,便于回溯和問題的排查;
其次,我們需要一個方便的機制查看宿主機上每個容器內(nèi)的系統(tǒng)和應(yīng)用產(chǎn)生的日志,例如:操作系統(tǒng)日志、IIS訪問日志、應(yīng)用的異常日志等;
對于容器的運行時的性能指標,docker的命令行工具,提供了docker stats命令,可以查看每個容器實時的CPU、內(nèi)存、IO能指標,我們可以考慮定時將它們收集保存起來,用于集中化的監(jiān)控。
另外,玩過Linux下docker的小伙伴們肯定知道,docker會將每個容器內(nèi)運行時打印到console的內(nèi)容,都記錄在宿主機的docker日志目錄中,而大多數(shù)Linux容器部署應(yīng)用,大多會將應(yīng)用自己的日志也打印到console,這樣,所有的日志都可以包含在docker宿主機的容器日志中。這有什么好處呢?好處就是,我們可以在宿主機上,配置日志解析工具,比如Logstash或fluentd,解析和forward所有日志。
在Windows Docker下,由于Windows的基因問題,一方面,大多數(shù)應(yīng)用都是基于IIS的應(yīng)用,沒辦法將日志直接打印到console,另一方面,IIS本身的日志和Windows的EventLog也無法方便地配置把它們打印到console,所以,一般的做法是,需要把這些日志所在的目錄,mount到宿主機,然后,再在宿主機上統(tǒng)一解析。特別對于Windows EventLog,它在Windows文件系統(tǒng)的格式無法被簡單讀取和解析,因此,我們一般需要用到一些地第三方工具,如nxlog和Elastic公司Beats(這兩個工具都是免費開源的)將解析后的Windows EventLog保存為易于解析的格式,比如JSON格式。
對于經(jīng)過解析的日志,現(xiàn)在比較流行的做法是把它導(dǎo)入Elasticsearch,這樣就可以方便通過kibana,grafana這樣的工具,可視化查看,遠程實時監(jiān)控了。
本想將相關(guān)組件都做成Windows Docker鏡像,方便大家能直接下載運行的,無奈這些組件都比較大,動輒幾十上百兆,國內(nèi)的網(wǎng)絡(luò)下,不FQ的情況下,我本機下載都很費勁,想必,做成Docker鏡像,大家運行的體驗也不會很好,所以,就先不做了。下面簡單介紹一下這幾個工具的使用,并分享一些核心的配置腳本,給大家做個參考。
首先是對IIS Log和Windows EventLog的解析,以nxlog為例:
nxlog的Windows版安裝完之后,是一個Windows Service。它的配置文件在C:\Program Files (x86)\nxlog\conf目錄下,每次更改nxlog.conf文件,都需要重啟nxlog service使配置生效。最經(jīng)常的用法,一般是將原始的IIS的W3C格式的日志,還有Windows EventLog解析為JSON格式,然后經(jīng)過Logstash中轉(zhuǎn)之后保存到Elasticsearch。
下面是一個典型的解析IIS W3C格式Log的nxlog.conf文件:
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調(diào)用w3c擴展組件,解析指定目錄的日志文件,做必要的規(guī)整;第三部分定義了如何保存解析結(jié)果,將解析后的消息,保存為JSON格式的鍵值對,然后寫入一個Logstash的TCP輸入端口。
下面是一個典型的nxlog解析Windows EventLog的例子:
define ROOT C:\Program Files (x86)\nxlogModuledir %ROOT%\modulesCacheDir %ROOT%\dataPidfile %ROOT%\data\nxlog.pidSpoolDir %ROOT%\dataLogFile %ROOT%\data\nxlog.log<Extension _syslog> Module xm_syslog</Extension><Extension _json> Module xm_json</Extension><Input eventlog> Module im_msvistalog ReadFromLast TRUE SavePos FALSE Query <QueryList>\ <Query Id='0'>\ <Select Path='System'>*</Select>\ <Select Path='Application'>*</Select>\ </Query>\ </QueryList> </Input><Output out> Module om_file File 'C:\temp\EventLog\' + $Hostname + '.json' <Exec> if out->file_size() > 20M { $newfile = 'C:\temp\EventLog\' + $Hostname + '_' + strftime(now(), '%Y%m%d%H%M%S') + '.json'; out->rotate_to($newfile); } </Exec> Exec to_json();</Output><Route 1> Path eventlog => out</Route>
這里,第一部分我們定義了如何從Windows EventLog中篩選消息;第二部分,定義了如何以每20M為大小,分割保存最新的EventLog為JSON。
將JSON格式的數(shù)據(jù)通過Logstash保存到Elasticsearch非常簡單,網(wǎng)上示例比比皆是,這理解不舉例了。
最后分享一個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文件真正執(zhí)行docker stats命令,獲取所有正在運行的容器的性能指標,其中具體的命令如下:
@echo offdocker stats --no-stream --format '{{.Container}} {{.Name}} {{.CPUPerc}} {{.MemUsage}} {{.NetIO}} {{.BlockIO}}'
所有JSON格式的監(jiān)控數(shù)據(jù)保存到Elasticsearch以后,使用kibana或者grafana進行數(shù)據(jù)的展示、設(shè)置監(jiān)控警報等等,就相對比較簡單了,目前也非常流行,網(wǎng)上應(yīng)該是能找到非常多的示例的,使用上也不存在Linux和Windows的區(qū)別,這里就不詳述了。對這方面有疑問的同學(xué),我們可以私下交流。
單節(jié)點Windows Docker服務(wù)器簡單運維下篇完。
我們簡單回顧一下,在最近的上下兩篇中,我們介紹了運維一個單節(jié)點Windows Docker服務(wù)器的主要思路和常用工具。這些思想和工具,也是更復(fù)雜的docker集群模式下的運維的基礎(chǔ)。運維的水很深,Windows Docker的運維,對大多數(shù)公司來說,也都還只是在摸索的過程中。文中的示例,更多的還在于拋磚引用,大家不要受其局限,要習慣于發(fā)揮想象力,創(chuàng)造性的解決問題,提出新的思路。
前面的示例中,雖然盡可能不依賴于Linux下的容器,但畢竟Linux下的容器和各種支持工具,現(xiàn)在已經(jīng)非常成熟了,在實際的部署中,還是應(yīng)該