本文要實現的初始化配置目標以下:node
- ansible配置ssh免密登陸;
- ansible遠程配置主機名;
- ansible控制遠程主機互相添加DNS解析記錄;
- ansible配置遠程主機上的yum鏡像源以及安裝一些軟件;
- ansible配置遠程主機上的時間同步;
- ansible關閉遠程主機上的selinux;
- ansible配置遠程主機上的防火牆;
- ansible遠程修改sshd配置文件並重啓sshd,使其更安全;
[root@nginx ansible]# tail -3 /etc/ansible/hosts #要初始的主機以下 [node] 192.168.20.4 192.168.20.5
playbook文件內容以下:linux
[root@nginx ansible]# cat ssh.yaml --- - name: configure ssh connection hosts: node gather_facts: false connection: local tasks: - name: configure ssh connection shell: | ssh-keyscan {{inventory_hostname}} >>~/.ssh/known_hosts sshpass -p '123.com' ssh-copy-id root@{{inventory_hostname}} ...
注:nginx
配置主機名可使用shell模塊,可是對於不太專業,ansible提供了一個專用於配置主機名的模塊:hostname模塊。shell
固然,要使用ansible去設置多個主機名,要求目標主機和目標名稱已經關聯好,不然多個主機和多個主機名之間沒法對應去設置。ubuntu
例如:分別設置node組中的兩個節點主機名爲node01和node02,playbook內容以下:vim
[root@ansible ansible]# cat test.yaml --- - name: set hostname hosts: node gather_facts: false vars: hostnames: - host: 192.168.20.4 name: node01 - host: 192.168.20.5 name: node02 tasks: - name: set hostname hostname: name: "{{item.name}}" when: item.host == inventory_hostname loop: "{{hostnames}}"
在上面的hostname模塊中,須要詳細介紹vars指令以及when、loop指令。centos
vars指令可用於設置變量,能夠設置一個或多個變量。下面幾種方式都是合理的:數組
# 設置單個變量 vars: var1: value1 vars: - var1: value1 # 設置多個變量 vars: var1: value1 var2: value2 vars: - var1: value1 - var2: value2
vars能夠設置在play級別,也能夠設置在task級別,設置在play級別,該play範圍內的task能夠訪問這些變量,其餘play範圍內則沒法訪問;設置在task級別,只有該task能訪問這些變量,其餘task和其餘play則沒法訪問。安全
例如:服務器
[root@ansible ansible]# cat test.yaml --- - name: play1 hosts: localhost gather_facts: false vars: - var1: "value1" tasks: - name: access var1 debug: msg: "var1's value: {{var1}}" - name: play2 hosts: localhost gather_facts: false tasks: - name: cat's access vars from play1 debug: var: var1 - name: set and access var2 in this task debug: var: var2 vars: var2: "value2" - name: cat't accesss var2 debug: var: var2
執行結果以下:
[root@ansible ansible]# ansible-playbook test.yaml PLAY [play1] ************************************************************************** TASK [access var1] ******************************************************************** ok: [localhost] => { "msg": "var1's value: value1" } PLAY [play2] ************************************************************************** TASK [cat's access vars from play1] *************************************************** ok: [localhost] => { "var1": "VARIABLE IS NOT DEFINED!" } TASK [set and access var2 in this task] *********************************************** ok: [localhost] => { "var2": "value2" } TASK [cat't accesss var2] ************************************************************* ok: [localhost] => { "var2": "VARIABLE IS NOT DEFINED!" } PLAY RECAP **************************************************************************** localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
回到咱們更改主機名的配置vars指令中:
vars: hostnames: - host: 192.168.20.4 name: node01 - host: 192.168.20.5 name: node02
上面只設置了一個變量hostnames,但這個變量的值是一個數組結構,數組的兩個元素又都是對象(字典/hash)結構。
因此想要訪問主機名node01和它的IP地址192.168.20.4,能夠:
tasks: - debug: var: hostnames[0].name - debug: var: hostnames[0].host
在ansible中,提供的惟一一個通用的條件判斷是when指令,當when指令的值爲true時,則執行該任務,不然不執行該任務。
例如:
[root@ansible ansible]# cat test.yaml --- - name: play1 hosts: localhost gather_facts: false vars: - myname: "Ray" tasks: - name: task will skip debug: msg: "myname is : {{myname}}" when: myname == "lv" - name: task will execute debug: msg: "myname is : {{myname}}" when: myname == "Ray"
在上面的myname值設置爲Ray,第一個任務由於when的判斷條件是myname==「lv」,因此判斷結果爲false,該任務不執行,同理,第二個任務由於when的值爲true,因此執行了。
該playbook的執行結果:
PLAY [play1] ************************************************************************** TASK [task will skip] ***************************************************************** skipping: [localhost] TASK [task will execute] ************************************************************** ok: [localhost] => { "msg": "myname is : Ray" } PLAY RECAP **************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
[root@ansible ansible]# cat add_dns.yaml --- - name: play1 hosts: node gather_facts: true tasks: - name: add DNS lineinfile: path: "/etc/hosts" line: "{{item}} {{hostvars[item].ansible_hostname}}" when: item != inventory_hostname loop: "{{ play_hosts }}"
執行結果以下:
TASK [Gathering Facts] **************************************************************** ok: [192.168.20.4] ok: [192.168.20.5] TASK [add DNS] ************************************************************************ skipping: [192.168.20.4] => (item=192.168.20.4) changed: [192.168.20.4] => (item=192.168.20.5) changed: [192.168.20.5] => (item=192.168.20.4) skipping: [192.168.20.5] => (item=192.168.20.5)
需求以下:
playbook以下:
[root@ansible ansible]# cat config_yum.yaml - name: config yum repo add install software hosts: node gather_facts: false tasks: - name: backup origin yum repos shell: cmd: "mkdir bak; mv *.repo bak" chdir: /etc/yum.repos.d creates: /etc/yum.repos.d/bak - name: add os repo and epel repo yum_repository: name: "{{item.name}}" description: "{{item.name}} repo" baseurl: "{{item.baseurl}}" file: "{{item.name}}" enabled: 1 gpgcheck: 0 reposdir: /etc/yum.repos.d loop: - name: os baseurl: "https://mirrors.tuna.tsinghua.edu.cn/centos/7/os/$basearch" - name: epel baseurl: "https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch" - name: install pkgs yum: name: lrzsz,vim,dos2unix,wget,curl state: present
在上面的yaml文件中,第一個任務是將全部系統默認的repo文件備份到bak目錄中,chdir參數表示在執行shell模塊的命令前先切換到/etc/yum.repos.d目錄下,creates參數表示bak目錄存在時則不執行shell模塊。
第二個任務是使用yum_repository模塊配置yum源,該模塊可添加或移除yum源。
相關參數以下:
- name:指定repo的名稱,對應於repo文件中的[name];
- description:repo的描述信息,對應repo文件中的name:xxx;
- baseurl:指定該repo的路徑;
- file:指定repo的文件名,不須要加.repo後綴,會自動加上;
- reposdir:repo文件所在的目錄,默認爲/etc/yum.repos.d目錄;
- enabled:是否啓用該repo,對應於repo文件中的enabled;
- gpgcheck:該repo是否啓用gpgcheck,對應於repo文件中的gpgcheck;
- state:present表示保證該repo存在,absent表示移除該repo。
在上面的配置中使用了一個loop循環來添加兩個repo:os和epel。
第三個任務是使用yum模塊安裝一些rpm包,yum模塊能夠更新、安裝、移除、下載包。
yum經常使用參數說明:
- name:指定要操做的包名
- 能夠帶版本號;
- 能夠是單個包名,也能夠是包名列表,或者逗號分隔多個包名;
- 能夠是url;
- 能夠是本地rpm包
- state:
- present和installed:保證包已安裝,它們是等價的別名;
- latest:保證包已安裝了最新版本,若是不是則更新;
- absent和removed:移除包,它們是等價的別名;
- download_only:僅下載不安裝包(ansible 2.7才支持)
- download_dir:下載包存放在哪一個目錄下(ansible 2.8才支持)
yum模塊是RHEL系列的包管理器,若是是ubuntu則沒法使用,可使用另外一個更爲通用的包管理器模塊:package,它能夠自動探測目標節點的包管理器類型並使用它們去管理軟件。大多數時候使用package來代替yum或代替apt-install等不會有什麼問題,可是有些包名在不一樣的操做系統上是不同的,這是須要注意的。
保證時間同步能夠避免不少玄學性的問題,特別是對集羣中的節點。
一般會使用ntpd時間服務器來保證時間的同步,這裏使用aliyun提供的時間服務器來保證時間同步,並將同步後的時間同步到硬件。
playbook文件以下:
--- - name: sync time hosts: node gather_facts: false tasks: - name: install and sync time block: - name: install ntpdate yum: name: ntpdate state: present - name: ntpdate to sync time shell: | ntpdate ntp1.aliyun.com hwclock -w
上面使用了一個block指令來組織了兩個有關聯性的任務,將他們做爲了一個總體。block更多的用於多個關聯性任務之間的異常處理。
關閉selinux的playbook以下:
[root@ansible roles]# cat disable_selinux.yaml --- - name: disable selinux hosts: node gather_facts: false tasks: - name: disable on the fly shell: setenforce 0 ignore_errors: true #因爲上條命令執行後的返回狀態碼不必定爲0,因此爲了防止非0報錯並中止palsybook接下來的任務,因此使用ignore_errors忽略錯誤 - name: disable forever in config lineinfile: path: /etc/selinux/config line: "SELINUX=disabled" #修改配置文件中的值,以便永久關閉 regexp: '^SELINUX=' #要修改的內容
注:ignore_errors也常常結合block使用,由於在block級別上設置異常處理,能夠處理block內部的全部錯誤。
playbook文件以下:
- name: Set Firewall hosts: node gather_facts: false tasks: - name: set iptables rule shell: | # 備份已有規則 iptables-save > /tmp/iptables.bak$(date +"%F-%T") # 給它三板斧 iptables -X iptables -F iptables -Z # 放行lo網卡和容許ping iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p icmp -j ACCEPT # 放行關聯和已創建鏈接的包,放行2二、44三、80端口 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT # 配置filter表的三鏈默認規則,INPUT鏈丟棄全部包 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT
有時候爲了服務器的安全,可能會去修改目標節點上sshd服務的默認配置,好比禁止root用戶登陸、禁止密碼認證登陸而只容許使用ssh密碼認證等。
在修改服務的配置文件時,通常有幾種方法:
相對來講,第三種方案是最統1、最易維護的方案。
此外,對於服務進程來講,修改了配置文件每每意味着要重啓服務,使其加載新的配置文件,對於sshd也同樣如此,可是sshd要比其餘服務特殊一些,由於ansible默認基於ssh鏈接,重啓sshd服務會使ansible鏈接斷開,好在ansible默認會重試創建鏈接,無非是多等待幾秒。但重建鏈接有可能會失敗,好比修改了配置文件不容許重試、修改了sshd的監聽端口等,這可能會使得ansible因鏈接失敗而沒法再繼續執行後續任務。
因此,在修改sshd配置文件時,有以下建議:
- 將此任務做爲初始化服務器的最後一個任務,即便鏈接失敗也無所謂;
- 在playbook中加入鏈接失敗的異常處理;
- 若是目標節點修改了sshd端口號,建議經過ansible自動或者咱們手動去修改inventory文件中的ssh鏈接端口號。
這裏爲了簡單,我準備使用lineinfile模塊去修改配置文件,要修改的內容只有兩項:
playbook內容以下:
[root@ansible roles]# cat sshd_config.yaml --- - name: modify sshd_config hosts: node gather_facts: false tasks: # 1.備份/etc/ssh/sshd_config文件 - name: backup sshd config shell: /usr/bin/cp -f {{path}} {{path}}.bak vars: - path: /etc/ssh/sshd_config # 2.設置PermitRootLogin no - name: disable root login lineinfile: path: "/etc/ssh/sshd_config" line: "PermitRootLogin no" insertafter: "^#PermitRootLogin" regexp: "^PermitRootLogin" notify: "restart sshd" # 3.設置PasswordAuthentication no - name: disable password auth lineinfile: path: "/etc/ssh/sshd_config" line: "PasswordAuthentication no" regexp: "^PasswordAuthentication yes" notify: "restart sshd" handlers: - name: "restart sshd" service: name: sshd state: restarted
關於notify和handlers的做用以下:
ansible會監控playbook執行後的changed的狀態,若是changed=1,則表示關注的狀態發生了改變,即本次任務的執行不具有冪等性,若是changed=0,則表示本次任務要麼沒執行,要麼執行了也沒有影響,即本次任務具有冪等性。
ansible提供了notify指令和handlers功能,若是在某個task中定義notify指令,當ansible在監控到該任務changed=1時,會觸發該notify指令所定義的handler,而後去執行handler。所謂handler,其實就是task,不管是在寫法上仍是做用上它和task都沒有什麼區別,惟一的區別在於handler是被觸發而被動執行的,不像普通task同樣會按流程正常執行。
惟一須要注意的是,notify和handler中任務的名稱必須一致。好比: notify: "restart sshd",那麼handlers中必須得有一個任務設置了 name: "restart sshd"。
此外,在上面的playbook中,兩個lineinfile任務都設置了相同的notify,但ansible不會屢次去重啓sshd,而是在最後重啓一次。實際上,ansible在執行完某個任務以後,並不會當即去執行對應的handler,而是在當前play中全部普通任務都執行完成後再去執行handler,這樣的好處是能夠屢次觸發notify,但最後只執行一次對應的handler,從而避免屢次重啓。
這裏將前面全部的playbook集合到單個playbook文件中去,這樣就能夠一次性執行全部任務。
整合後的playbook以下:
--- - name: Configure ssh Connection hosts: node gather_facts: false connection: local tasks: - name: configure ssh connection shell: | ssh-keyscan {{inventory_hostname}} >>~/.ssh/known_hosts sshpass -p'123.com' ssh-copy-id root@{{inventory_hostname}} - name: Set Hostname hosts: node gather_facts: false vars: hostnames: - host: 192.168.20.4 name: node01 - host: 192.168.20.5 name: node02 tasks: - name: set hostname hostname: name: "{{item.name}}" when: item.host == inventory_hostname loop: "{{hostnames}}" - name: Add DNS For Each hosts: node gather_facts: true tasks: - name: add DNS lineinfile: path: "/etc/hosts" line: "{{item}} {{hostvars[item].ansible_hostname}}" when: item != inventory_hostname loop: "{{ play_hosts }}" - name: Config Yum Repo And Install Software hosts: node gather_facts: false tasks: - name: backup origin yum repos shell: cmd: "mkdir bak; mv *.repo bak" chdir: /etc/yum.repos.d creates: /etc/yum.repos.d/bak - name: add os repo and epel repo yum_repository: name: "{{item.name}}" description: "{{item.name}} repo" baseurl: "{{item.baseurl}}" file: "{{item.name}}" enabled: 1 gpgcheck: 0 reposdir: /etc/yum.repos.d loop: - name: os baseurl: "https://mirrors.tuna.tsinghua.edu.cn/centos/7/os/$basearch" - name: epel baseurl: "https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch" - name: install pkgs yum: name: lrzsz,vim,dos2unix,wget,curl state: present - name: Sync Time hosts: node gather_facts: false tasks: - name: install and sync time block: - name: install ntpdate yum: name: ntpdate state: present - name: ntpdate to sync time shell: | ntpdate ntp1.aliyun.com hwclock -w - name: Disable Selinux hosts: node gather_facts: false tasks: - block: - name: disable on the fly shell: setenforce 0 - name: disable forever in config lineinfile: path: /etc/selinux/config line: "SELINUX=disabled" regexp: '^SELINUX=' ignore_errors: true - name: Set Firewall hosts: node gather_facts: false tasks: - name: set iptables rule shell: | # 備份已有規則 iptables-save > /tmp/iptables.bak$(date +"%F-%T") # 給它三板斧 iptables -X iptables -F iptables -Z # 放行lo網卡和容許ping iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p icmp -j ACCEPT # 放行關聯和已創建鏈接的包,放行2二、44三、80端口 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT # 配置filter表的三鏈默認規則,INPUT鏈丟棄全部包 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT - name: Modify sshd_config hosts: node gather_facts: false tasks: - name: backup sshd config shell: /usr/bin/cp -f {{path}} {{path}}.bak vars: - path: /etc/ssh/sshd_config - name: disable root login lineinfile: path: "/etc/ssh/sshd_config" line: "PermitRootLogin no" insertafter: "^#PermitRootLogin" regexp: "^PermitRootLogin" notify: "restart sshd" - name: disable password auth lineinfile: path: "/etc/ssh/sshd_config" line: "PasswordAuthentication no" regexp: "^PasswordAuthentication yes" notify: "restart sshd" handlers: - name: "restart sshd" service: name: sshd state: restarted