前言
上一篇文章概括性的介紹了Salt的用途和它的基本組成和實現原理,也深入的的介紹了Salt的命令編排和批量執行,但是對於狀態管理只是簡單的介紹了一下,因為狀態管理是一個比較重要且常用的功能,單獨的介紹狀態管理會比較適合。本文將會首先介紹Salt狀態管理的一些概念,然后會通過實例來演示Salt狀態管理的使用,實例的演示基於Vagrant和Vagrant的Salt插件。
Salt狀態管理的關鍵概念
狀態樹
在Salt中,所有的狀態都是通過狀態描述文件來定義的,而它們都存儲在master節點(masterless情況除外)。Salt通過狀態樹定義了不同'環境'下狀態描述文件的層次結構。如下圖:

如上圖所示,狀態樹由的根節點是master的配置文件/etc/salt/master,它通過'file_roots'配置項定義了不同環境下配置文件所存在的目錄。‘環境’這個概念的主要是用於分門別類的存放不同用途的狀態描述文件。例如,一個公司的服務器集群通常有不同的用途,大部分機器是用於線上環境,但是也還有一部分機器用於開發和測試。因為機器的用途不同,所以他們除了一些基礎配置相同外,大部分配置是大相徑庭的。Salt考慮到了這一點,他通過’環境‘將不同用途的狀態描述文件隔離在不同的目錄,然后通過base環境下的'top.sls'文件描述該環境下哪些minion應該處於哪種狀態。base環境是默認的基礎環境,它可以用於存放一些基礎的狀態描述文件,如每個機器都需要的ldap、ntp、監控等。其它環境的定義是可以按自己的需要自定義的,如上圖,通常可以定義dev,qa和prod環境分別代表開發、測試和生產環境。
salt-master配置文件中的file_roots定義了環境,如下:
# Master file_roots configuration:
file_roots:
base:
- /srv/salt/base
dev:
- /srv/salt/dev
qa:
- /srv/salt/qa
prod:
- /srv/salt/prod
base環境下的'top.sls'文件描述該環境下哪些minion應該處於哪種狀態,如下:
base:
'*':
- global
dev:
'webserver*dev*':
- webserver
'db*dev*':
- db
qa:
'webserver*qa*':
- webserver
'db*qa*':
- db
prod:
'webserver*prod*':
- webserver
'db*prod*':
- db
如上,集群中所有的minions都會使用/srv/salt/base/global.sls定義的狀態;fqdn匹配'webserver*dev*'的minions的會使用/srv/salt/dev/webserver.sls所定義的狀態,其它類似。Salt仍然是使用Targeting的功能來選取節點,所以選取的方式有很多種。
Salt中狀態樹拓撲結構的定義由salt-master配置文件中的'file_roots'和base環境下的top.sls文件組成。狀態的具體定義是由存儲在這些目錄下的sls文件描述。
關於狀態樹的更多信息,請閱讀:http://salt.readthedocs.org/en/latest/ref/states/top.html
狀態描述文件
<Include Declaration>:
- <Module Reference>
- <Module Reference>
<Extend Declaration>:
<ID Declaration>:
[<overrides>]
# standard declaration
<ID Declaration>:
<State Declaration>:
- <Function>
- <Function Arg>
- <Function Arg>
- <Function Arg>
- <Name>: <name>
- <Requisite Declaration>:
- <Requisite Reference>
- <Requisite Reference>
# inline function and names
<ID Declaration>:
<State Declaration>.<Function>:
- <Function Arg>
- <Function Arg>
- <Function Arg>
- <Names>:
- <name>
- <name>
- <name>
- <Requisite Declaration>:
- <Requisite Reference>
- <Requisite Reference>
# multiple states for single id
<ID Declaration>:
<State Declaration>:
- <Function>
- <Function Arg>
- <Name>: <name>
- <Requisite Declaration>:
- <Requisite Reference>
<State Declaration>:
- <Function>
- <Function Arg>
- <Names>:
- <name>
- <name>
- <Requisite Declaration>:
- <Requisite Reference>
上表給出了一個比較完整的狀態描述文件的結構,這是用yaml格式來描述的。Yaml格式和jinja2模板是Salt默認提供的狀態文件描述格式,同時Salt也支持不同類型的描述文件,他們通過Render模塊支持,例如xml等。在此,我們以默認的yaml格式進行介紹。
我們先看一個實際的例子,example.sls:
vim: <ID Declaration>
pkg: <State Declaration>
- installed <Function>
salt: <ID Declaration>
pkg: <State Declaration>
- latest <Function>
service.running: <State Declaration>.<Function>
- require: <Requisite Declaration>
- file: /etc/salt/minion <Requisite Reference>
- pkg: salt <Requisite Reference>
- names: <Names>
- salt-master <Name>
- salt-minion <Name>
- watch: <Requisite Declaration>
- file: /etc/salt/minion <Requisite Reference>
/etc/salt/minion: <ID Declaration>
file.managed: <State Declaration>.<Function>
- source: salt://salt/minion <Function Arg>
- user: root <Function Arg>
- group: root <Function Arg>
- mode: 644 <Function Arg>
- require: <Requisite Declaration>
- pkg: salt <Requisite Reference>
<ID Declaration>ID聲明
在這個例子中首先通過<ID Declaration>定義了三個狀態描述模塊,他們分別是vim,salt和/etc/salt/minion。在<ID Declaration>下包含了<State Declaration><Function>等定義,這些定義具體描述了vim, salt和/etc/salt/minion這三個模塊具體是由哪些狀態組件組成,使用了狀態組件的哪些功能和具體的參數,它們之間的依賴關系是什么。同時,如果<State Declaration>下沒有定義<Name Declaration>或<Names Declaration>那么<ID Declaration>將會默認成為<State Declaration>下的Name參數。就如同下面兩個狀態描述是等價的,他們都定義了使用pkg這個狀態組件將vim這個包處於安裝狀態。
vim:
pkg:
-installed
和
editor:
pkg:
- installed
- name: vim
<ID Declaration>在整個狀態樹中必須是單一的,它是其它狀態描述模塊引用它的Key。如果在狀態樹中出現兩個同名的<ID Declaration>,Salt只會識別第一個被加載的狀態定義模塊。
<Name Declaration>和<Names Declaration>聲明
<Name Declaration>和<Names Declaration>都定義在<State Declaration>下,可以把它們看作是State下某個Function的參數,其中<Names Declaration>就是一個參數數組。如在salt中的service狀態模塊的描述中,就使用salt-mastre和salt-minion作為<Names Declaration>,定義了這兩個服務處於安裝狀態。
這兩個聲明的使用可以解決一些實際中的問題,如避免ID沖突,縮短ID聲明等,可參考:
http://salt.readthedocs.org/en/latest/ref/states/highstate.html#name-declaration
http://salt.readthedocs.org/en/latest/ref/states/highstate.html#names-declaration
<State Declaration>狀態聲明
狀態聲明下包含了功能<Function>、功能參數<Function Arg>、Name、Names和表示狀態之間的關系的聲明<Requisite Declaration>(狀態之間的關系在后面一節介紹)。
其實從狀態聲明的數據結構,並結合上一篇文章說講到的命令編排來看,我們可以隱約的察覺出salt的狀態管理其實也是使用了由minions所提供的不同狀態組件,就如同命令編排中不同的module。在狀態描述文件中,通過使用<State Declaration>和<Function>指定了使用狀態組件的某個函數,並將Function Arg, Name和Names傳遞到該函數執行。所以這也驗證了Salt本質上是一個可批量執行的遠程命令編排系統,它的其它擴展功能,包括狀態管理也是基於這樣一個系統構建。
Salt提供了這豐富的狀態組件用於實現狀態管理,如常見的包管理、服務管理、文件管理等,參考:http://docs.saltstack.com/ref/states/all/index.html
如本例中salt和vim模塊都使用了pkg組件,並分別使用了latest和installed函數,這些我們都可以在該組件的文檔中找到:
http://docs.saltstack.com/ref/states/all/salt.states.pkg.html#salt.states.pkg.installed
http://docs.saltstack.com/ref/states/all/salt.states.pkg.html#salt.states.pkg.latest
從文檔的描述,我們可以知道installed函數保證了包處於安裝狀態,latest函數確保了包處於安裝狀態並且是最新的。
在知曉工作原理的前提下,我們可以很輕松的通過文檔學習狀態管理的使用,並且能自定義的擴展狀態組件。
狀態之間的關系
在Salt中,<Include Declaration>可以用於應用引用位於其它.sls文件下的<ID Declaration>,就如同c語言的inlcude語句一樣。例如位於base環境中的一個sls文件:
include:
- apache # include /srv/salt/base/apache.sls
extend:
apache: # extend the descrpition of apache
service:
- watch:
- file: mywebsite
mywebsite:
file:
- managed
這個文件首先用<Include Declaration>引用了apache這個狀態文件,因為是位於base環境,所以實際引用的是/srv/salt/base/apache.sls文件。如果apache.sls文件位於/sav/salt/base/web/apache.sls,那么在include時應該指明是web.apache.
extend語句對apache的定義進行了擴展(apache.sls中已經對apache進行了定義),這個功能相當於c++中子類對父類進行擴展。
在這只要明確如果需要引用位於其它sls文件中的<ID Declaration>就必須先用include文件引用該sls文件。
除此之外,Salt還提供了7種<Requisite Declaration>,用於實現狀態之間的依賴,它們分別是require, require_in, watch, watch_in, prereq, prereq_in, use。
require, require_in
require聲明了本狀態依賴於指定的狀態,require_in聲明了本狀態被指定狀態依賴。A require B <=> B require_in A。通過require指令,我們就可以指定一個狀態收斂的順序,如先安裝vim再配置vim的配置文件。
vim:
pkg.installed
/etc/vimrc:
file.managed:
- source: salt://edit/vimrc # get from master's file server:/srv/salt/[env]/edit/vimrc
- require:
- pkg: vim
等價於
vim:
pkg.installed:
- require_in:
- file: /etc/vimrc
/etc/vimrc:
file.managed:
- source: salt://edit/vimrc
watch, watch_in
watch和watch_in是require和require_in的擴展,唯一的區別是watch和watch_in會額外的調用狀態組件中的mod_watch函數,如果狀態組件沒有提供該函數,那么它和require, require_in的行為完全一樣。
如本節的第一個例子,通過include apache並擴展了對apache的定義,將service.runing的watch設置成了mywebsite。那么,mywebsite狀態的改變將觸發調用service的mod_watch函數,重啟apache服務。
prereq, prereq_in
prereq, prereq_in同樣是指明了本狀態的執行依賴於指定的狀態。但是與require不同的地方是,當A require B,那么狀態的收斂順序是,先B后A,如果B失敗,A不會執行;當A prereq B時,系統先會用(test=True)去測試B狀態是否會改變(B過程並未實際執行),如果B狀態會改變,那么先執行A狀態,再執行B狀態,如果A執行失敗,那么B就不執行了。prereq就是pre request的意思。
這兩個聲明通常用於分布式服務中,部署升級時先將服務從負載均衡中摘除,在進行代碼升級。例如:
graceful-down:
cmd.run:
- name: service apache graceful
- prereq:
- file: site-code
site-code:
file.recurse:
- name: /opt/site_code
- source: salt://site/code
當通過salt master代用更新了/opt/site_code下的代碼文件時,salt-minion上的file組件會先對比本地的代碼文件是否與master上的不一致,如果不一致說明site-code這個狀態會變化,那么先執行graceful-down這個狀態,apache在服務完當前的請求后會shutdown,如果前端的負載均衡器有心跳包檢查機制,會自動將請求分發到其它的節點。這時在實際執行更新代碼的操作,從master上的file server下載最新的site-code文件。
use
use聲明可以簡化配置,復用指定狀態的配置。例如:
/etc/foo.conf:
file.managed:
- source: salt://foo.conf
- template: jinja
- mkdirs: True
- user: apache
- group: apache
- mode: 755
/etc/bar.conf
file.managed:
- source: salt://bar.conf
- use:
- file: /etc/foo.conf
等價於
/etc/foo.conf:
file.managed:
- source: salt://foo.conf
- template: jinja
- mkdirs: True
- user: apache
- group: apache
- mode: 755
/etc/bar.conf
file.managed:
- source: salt://bar.conf
- template: jinja
- mkdirs: True
- user: apache
- group: apache
- mode: 755
用模板動態生成的狀態文件
Salt除了可以靜態地描述狀態文件,同時還支持動態生成的狀態文件,使用者可以通過Jinja2模板並結合Grains或Pillar等功能,對狀態文件進行編程,動態生成狀態文件。
apache:
pkg.installed:
{% if grains['os'] == 'RedHat' %}
- name: httpd
{% elif grains['os'] == 'Ubuntu' %}
- name: apache2
{% endif %}
通過Grains提供的操作系統信息動態的指定pkg組件使用apache2或httpd安裝包。
更詳細內容請參考:
http://salt.readthedocs.org/en/latest/topics/tutorials/states_pt3.html
http://salt.readthedocs.org/en/latest/topics/tutorials/states_pt4.html
狀態收斂的運行過程
Salt的狀態管理由state模塊完成,一個命令'salt '*' state.highstate'就會觸發所有的minions進行狀態收斂。整個過程大致如下:
- master通過命令'salt [Targeting] state.highstate'調用指定的minions的state模塊的highstate函數;
- minion的state模塊訪問master的file server,通過top.sls的定義,file server會將minion所屬環境下的狀態文件和靜態文件傳輸給minion;
- minion對狀態文件進行編譯,從highdata到lowdata,生成了具體的狀態收斂順序;
- minion中的state模塊根據狀態收斂的順序執行該狀態說指定的狀態組件,如pkg, service, file等;
- minion中的state將執行結果返回給master;
實例:使用Vagrant和Salt配置開發環境
需求
- 單節點
- 安裝vim並從git倉庫中拉取配置文件
- 使用ubuntu 12.04 64位操作系統
- 整個過程全部自動化
實現步驟
- 安裝virtualbox和vagrant;
- 安裝vagrant-salt插件,'$vagrant plugin install vagrant-salt';
- 參考salty-vagrant的手冊,編寫Vagrant配置文件;
- 參考本文內容編寫Salt的配置文件;
- 運行'$vagrant up'構建開發環境;
全部文件:https://github.com/AlexYangYu/example-vagrant-salt
