老司機實戰Windows Server Docker:4 單節點Windows Docker伺服器簡單運維(下)
上篇中,我們主要介紹了使用docker-compose對Windows Docker單伺服器進行遠端管理,編譯和部署映象,並且設定容器的自動啟動。但是,還有一些重要的問題沒有解決,這些問題不解決,就完全談不上運維:
問題一:如此部署的應用,在宿主機外部,只能通過宿主機的ip加一個個特定的埠來訪問每個容器內的應用,這顯然是不滿足實際需求的。
問題二:相比於將應用直接部署在有UI介面的Windows Server,因為每個應用部署於自己的Windows Docker容器,當應用執行時發生各種問題時,比如,cpu高,記憶體高,訪問變慢等等,如何才能方便地排查問題呢?即使你願意一個個容器attach上去,也因為它沒有UI,遠沒有傳統有UI介面的Windows Server上容易。所以,我們必須有必要的工具來更方便的監控容器的執行。
下篇
- 負載均衡和反向代理
- 日誌解析和監控
負載均衡和反向代理
要解決問題一:
- 首先,我們需要一個機制,當用戶訪問我們的ip或域名時,伺服器能根據不同的域名,或者不同的子路徑,在相同的埠比如80或者443,從每個應用容器返回內容——這就是反向代理;
- 接著,我們不希望我們的應用在更新、系統維護、或者區域性故障時無法提供服務,所以,每個部署的應用不能是單點,那麼,如果同一個應用部署了多個容器例項,如何才能讓他們Serve相同的域名和URL請求呢?——這就是負載均衡;
通常,支援反向代理的元件,往往也同時提供負載均衡功能。例如: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服務。
FROM microsoft/windowsservercore:latest
ADD vc_redist.x64.exe /vc_redist.x64.exe
RUN /vc_redist.x64.exe /q
ADD 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 /conf
RUN del c:\Apache24\conf\httpd.conf
RUN del c:\Apache24\conf\extra\httpd-vhosts.conf
RUN 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目錄下執行docker-compose up,稍作等待,等容器執行起來,然後,在宿主機外部,注意一定是從宿主機外部(原因上一篇文章有解釋),訪問宿主機的ip地址下的/iis-demo/路徑,可以看到,iis-demo的預設頁面內容能夠被正常顯示,說明反向代理和負載均衡已經正常運行了。
其他備註:
- 這裡的Apache配置是出於演示目的,拋磚引玉,極度簡化的版本,如果用於真實環境,請根據Apache官方文件以和相應的效能測試,做必要調整;
- 除了Apache之外,nginx可以運行於Windows,但是效能不佳,官方不推薦在Windows下使用;
- HAProxy只能運行於Linux;
- IIS的ARR,反向代理功能尚可,但是負載均衡依賴於IIS的叢集,限制頗多,如果同一個應用的容器只需要部署單個例項的話可以考慮;
- 這裡在docker-compose.yml中定義同一個應用的兩個服務的做法,只是在不使用docker的叢集化部署時的簡化做法,如果應用了Swarm叢集,或者使用了其他支援叢集和自動擴充套件的容器編排方案,是可以直接通過docker-compose的scale引數,讓一個docker-compose服務執行多個例項的,這個我們以後會聊到;
日誌解析和監控
要解決問題二:
- 首先,我們需要一個方便的機制檢視宿主機上每個容器執行時的CPU,記憶體,IO等資源開銷,還要能保留這些執行情況的歷史紀錄,便於回溯和問題的排查;
- 其次,我們需要一個方便的機制檢視宿主機上每個容器內的系統和應用產生的日誌,例如:作業系統日誌、IIS訪問日誌、應用的異常日誌等;
對於容器的執行時的效能指標,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目錄下,每次更改nxlog.conf檔案,都需要重啟nxlog service使配置生效。最經常的用法,一般是將原始的IIS的W3C格式的日誌,還有Windows EventLog解析為JSON格式,然後經過Logstash中轉之後儲存到Elasticsearch。
下面是一個典型的解析IIS W3C格式Log的nxlog.conf檔案:
define ROOT C:\Program Files (x86)\nxlog
Moduledir %ROOT%\modules
CacheDir %ROOT%\data
Pidfile %ROOT%\data\nxlog.pid
SpoolDir %ROOT%\data
LogFile %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的例子:
define ROOT C:\Program Files (x86)\nxlog
Moduledir %ROOT%\modules
CacheDir %ROOT%\data
Pidfile %ROOT%\data\nxlog.pid
SpoolDir %ROOT%\data
LogFile %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格式的資料通過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命令,獲取所有正在執行的容器的效能指標,其中具體的命令如下:
@echo off
docker stats --no-stream --format "{{.Container}} {{.Name}} {{.CPUPerc}} {{.MemUsage}} {{.NetIO}} {{.BlockIO}}"
所有JSON格式的監控資料儲存到Elasticsearch以後,使用kibana或者grafana進行資料的展示、設定監控警報等等,就相對比較簡單了,目前也非常流行,網上應該是能找到非常多的示例的,使用上也不存在Linux和Windows的區別,這裡就不詳述了。對這方面有疑問的同學,我們可以私下交流。
單節點Windows Docker伺服器簡單運維下篇完。
我們簡單回顧一下,在最近的上下兩篇中,我們介紹了運維一個單節點Windows Docker伺服器的主要思路和常用工具。這些思想和工具,也是更復雜的docker叢集模式下的運維的基礎。運維的水很深,Windows Docker的運維,對大多數公司來說,也都還只是在摸索的過程中。文中的示例,更多的還在於拋磚引用,大家不要受其侷限,要習慣於發揮想象力,創造性的解決問題,提出新的思路。
前面的示例中,雖然儘可能不依賴於Linux下的容器,但畢竟Linux下的容器和各種支援工具,現在已經非常成熟了,在實際的部署中,還是應該根據實際情況和Windows Docker結合使用,只要能解決問題的工具和方法就都是極好的!