Ansible 實戰:基於docker運行ansible

準備好安裝了Ansible的主機,作爲實驗環境。
有一臺新安裝的Centos7系統,利用ansible在主機上完成以下操作:

  • 主機初始化設置,主要是yum
  • 安裝python3
  • 安裝docker,並且製作一個ansible的鏡像
  • SSH和用戶賬號安全設置

準備工作

準備好一臺安裝了Centos7系統的主機,然後去ansible的主機上對目標主機進行操作。
我這裏的ansible也是新安裝的,所以如果遇到一些沒有的工具,也會在遇到的時候一步一步進行添加。下面是彙總的內容。

對於一臺新裝好的ansible,還是會缺少很多依賴項,有些和特定模塊關聯的比較緊密,可以在使用特定模塊的時候再安裝。
還有一些使用範圍比較廣,建議先裝好:

  • yum 安裝 sshpass
  • 目標主機安裝了python3,就需要將pip更新到最新,並且安裝好selinux

修改ansible.cfg配置文件
我的實驗環境,爲了測試方便,暫時只有下面3項做了設置:

[defaults]
#host_key_checking = False
inventory = ~/hosts
vault_password_file = ~/vault_password_file

配置文件第一行的[defaults]不能省。

添加主機

編輯 /etc/ansible/hosts 添加主機信息:

host1 ansible_host=192.168.24.172

我的實驗環境是直接放到了家目錄裏了

先用ping模塊測試一下連通性:

[root@Ansible ~]# ansible host1 -m ping -k
SSH password: 
host1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
[root@Ansible ~]# 

這裏使用了-k參數,手動輸入密碼。
測試聯通性,可以再加上-c參數,指定local。不過這裏要驗證一下ssh連接。

安裝 sshpass
如果是第一次使用,可能會提示需要安裝sshpass。這個簡單,yum安裝即可:

$ yum install sshpass

添加密碼信息
最好的做法是用SSHKey實現免密碼登錄。這裏講使用密碼的情況。
爲了方便,也可以把密碼加到配置中,這樣不用每次都輸入密碼:

host2 ansible_host=192.168.24.172 ansible_ssh_pass=123456

這樣做,密碼就是明碼存放了,很不安全。下面有更安全的做法。

加密保存密碼

使用 ansible-vault 來加密敏感信息

修改配置文件
這步不是必須的,運行命令的時候可以加上參數 --vault_password_file 來指定你的密碼文件。不過通過設置就可以把這個參數省了:

$ vi /etc/ansible/ansible.cfg
# If set, configures the path to the Vault password file as an alternative to
# specifying --vault-password-file on the command line.
vault_password_file = ~/vault_password_file

使用openssl來生成隨機密碼
使用 openssl 來生成隨機的base64編碼的內容:

$ openssl rand -base64 128 -out vault_password_file
$ chmod a-w vault_password_file
$ chattr +i vault_password_file

文件生成後,對文件做了一些保護,防止文件被修改。修改文件的ugo權限只對普通用戶有效,root依然可以修改文件。後面的命令可以鎖定文件,即使root也無法修改了,防止對文件的意外操作。
如果需要修改或者刪除文件,可以先把文件解鎖,只要把命令的 +i 改成 -i 就可以了:

$ chattr -i vault_password_file

生成加密後的字符串
下面是使用 ansible-vault 來生成加密後的字符串:

[root@Ansible ~]# ansible-vault encrypt_string "123456" --name ansible_ssh_pass
ansible_ssh_pass: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          33336539373437373161326537323836343163633532396235383334326562303134626565613537
          3134313631393931376361363761313165393966613831360a343338353765326331663433613533
          31636238613133363639336130613264386366363931663230333663363062333836323730383563
          6562626265393535310a623732633863633765363066636265303265316661373464323961666131
          6561
Encryption successful
[root@Ansible ~]# 

接下來需要把這裏獲得的信息複製到配置文件中。

修改主機配置文件
這裏對配置文件進行了大修改,原來是INI格式的,現在改成了YAML格式:

---
all:
  hosts:
    host1:
      ansible_host: 192.168.24.172
      ansible_ssh_pass: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          33336539373437373161326537323836343163633532396235383334326562303134626565613537
          3134313631393931376361363761313165393966613831360a343338353765326331663433613533
          31636238613133363639336130613264386366363931663230333663363062333836323730383563
          6562626265393535310a623732633863633765363066636265303265316661373464323961666131
          6561
...

要使用YAML是因爲INI格式不支持內嵌vault,官方的說明如下:

This is an example using an extract from a YAML inventory, as the INI format does not support inline vaults:

INI格式的主機配置文件,可讀性感覺更好。如果依然希望使用INI格式的配置,可以另外再創建vars文件,把額外的參數以YAML格式寫在另一個文件中。

驗證
這次用command模塊了進行驗證:

$ ansible host1 -a hostname
host1 | CHANGED | rc=0 >>
Host1

$ 

至此準備工作都做完了,已經能成功連接到主機。

python3 版本的問題

python多個版本並存是很常見的情況。Centos7系統安裝後默認就有一個python2,一般自己要使用python3就會再安裝一個,這個就會帶來一些問題。
之前遇到的問題,就是ansible使用了目標主機的python3來執行任務,而有模塊使用python3會有問題。
之前有問題的主機是將系統的python命令指向了python3。並且去yum相關的命令中修改了開頭的#!/usr/bin/python2來保證yum可以正常運行。
而ansible默認就會去目標主機查找 /usr/bin/python 來執行,這就造成了默認使用python3來執行的情況。
這裏就強行將ansible_python_interpreter的值指向目標主機的python3來將問題再現出來。

yum 模塊

yum 是用python2寫的,所以不支持python3。下面是報錯的情況:

[root@Ansible ~]# ansible host1 -m yum -a "name=wget state=present" -e "ansible_python_interpreter=/usr/bin/python3"
host1 | FAILED! => {
    "ansible_facts": {
        "pkg_mgr": "yum"
    },
    "changed": false,
    "msg": "The Python 2 bindings for rpm are needed for this module. If you require Python 3 support use the `dnf` Ansible module instead.. The Python 2 yum module is needed for this module. If you require Python 3 support use the `dnf` Ansible module instead."
}
[root@Ansible ~]#

根據msg的提示。建議使用dnf,但是嘗試之後還是失敗了。
簡單的辦法就是指定 ansible_python_interpreter 參數,使用Python2。
使用yum模塊必須指定python2,暫時沒別的辦法。

selinux

在使用 get_utl 模塊的時候,就會遇到selinux的問題。這類問題應該只要把selinux關掉應該就解決了(沒試過),不過這裏看看不關的情況。

[root@Ansible ~]# ansible host1 -m get_url -a 'url="http://mirrors.aliyun.com/repo/epel-7.repo" dest="/etc/yum.repos.d/epel-7.repo"' -e "ansible_python_interpreter=/usr/bin/python3"
host1 | FAILED! => {
    "changed": false,
    "msg": "Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!"
}
[root@Ansible ~]#

一個辦法還是使用python2,因爲系統默認已經安裝好了python2版本的selnux,應該就是下面這個:

[root@PlayHost ~]# yum info libselinux-python
已加載插件:fastestmirror
Loading mirror speeds from cached hostfile
 * base: ftp.sjtu.edu.cn
 * extras: ftp.sjtu.edu.cn
 * updates: ftp.sjtu.edu.cn
已安裝的軟件包
名稱    :libselinux-python
架構    :x86_64
版本    :2.5
發佈    :14.1.el7
大小    :589 k
源    :installed
來自源:anaconda
簡介    : SELinux python bindings for libselinux
網址    :https://github.com/SELinuxProject/selinux/wiki
協議    : Public Domain
描述    : The libselinux-python package contains the python bindings for developing
         : SELinux applications.

[root@PlayHost ~]# 

如果使用python2也就沒這個問題了。

使用python3也是可以的,只要把這個包裝上。在yum裏沒有找到對應的python3版本,不過pip裏有:

[root@PlayHost ~]# pip install selinux
Collecting selinux
  Downloading https://files.pythonhosted.org/packages/59/38/780baac7aafcf44cca8e77318ec935660f93fc30e3de92f8de7a7dbc0513/selinux-0.1.6-py2.py3-none-any.whl
Installing collected packages: selinux
Successfully installed selinux-0.1.6
[root@PlayHost ~]#

這裏的操作是在目標主機上進行的,也可以用ansible遠程來操作。不過這裏主要是爲了講明白問題。
建議在目標主機上安裝python3之後,就順便把selinux也裝上。這樣有些python2和python3都兼容的模塊使用任何版本都不會有問題

pip

初始的系統是沒有pip的,之後我也只會爲python3安裝pip。並且所有需要pip安裝的python模塊都是爲python3安裝的。
所以在安裝pip的時候,需要指定 ansible_python_interpreter 參數,使用Python3。這個和yum模塊正好相反。
使用pip模塊的時候,可以加參數 extra_args: -i https://mirrors.aliyun.com/pypi/simple/ 來指定pypi源。
不過爲方便起見,也可以直接寫在配置文件中指定默認的pypi源。配置文件也用命令可以添加,不用編輯文件。使用的命令如下:

$ pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/

運行命令可以用command模塊來實現。

安裝pip和更新
安裝我是用yum模塊來安裝python36-pip(使用easy_install模塊安裝會有問題,我解決不了)。安裝完成後,再用pip模塊更新pip。
更新了pip之後,再順便把selinux也裝上,就是上一小節的問題。

實戰 playbook

這裏要用自己測試環境的ansible去配置一臺主機。完成初始配置後,要安裝好python3和docker,然後再創建一個ansible的鏡像。最後還有SSH和賬戶的安全設置。

yum 安裝工具

主要是設置國內的鏡像源,然後使用yum來安裝工具:

---
- hosts: host1
  vars:
      ansible_python_interpreter: /usr/bin/python2  # 指定使用python2,防止有坑

  tasks:
    - name: 刪除默認的repo文件
      file:
        path: "/etc/yum.repos.d/{{ item }}"
        state: absent
      with_items:
        - CentOS-Base.repo
        - CentOS-Debuginfo.repo
        - CentOS-Media.repo
        - CentOS-Vault.repo
        - CentOS-CR.repo
        - CentOS-fasttrack.repo
        - CentOS-Sources.repo
    - name: 下載阿里源的 Centos-7.repo epel-7.repo
      get_url:
        url: "http://mirrors.aliyun.com/repo/{{ item.url }}"
        dest: "/etc/yum.repos.d/{{ item.dest }}"
      with_items:
        - {url: "Centos-7.repo", dest: "CentOS-7.repo"}
        - {url: "epel-7.repo", dest: "epel-7.repo"}
    - name: yum安裝 wget net-tools bind-utils net-snmp-utils traceroute lrzsz
      yum:
        name:
          - wget
          - net-tools
          - bind-utils
          - net-snmp-utils
          - traceroute
          - lrzsz
        state: present
...

安裝 python3

使用yum來安裝python3和pip,然後再更新pip到最新版本,並設置國內的pip鏡像源:

---
- hosts: host1
  tasks:
    - name: yum 安裝 python3
      yum:
        name: [python36, python36-setuptools, python36-pip]
        state: present
    - name: 更新 pip
      # 有 easy_install 安裝會報錯,這裏用 pip 更新
      pip: 
        name: pip
        state: latest
        extra_args: -i https://mirrors.aliyun.com/pypi/simple/
      # 用python3的pip要指定python3,否則有可能去調用python2的pip
      vars: {ansible_python_interpreter: /usr/bin/python3}
    - name: 檢查pip配置文件是否存在
      file:
        path: ~/.config/pip/pip.conf
        state: file
      ignore_errors: True
      register: result
    - name: 如果pip配置文件不存在:設置使用阿里源
      command: pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
      when: result is failed
    - name: 安裝 selinux
      pip: {name: selinux, state: present}
      vars: {ansible_python_interpreter: /usr/bin/python3}
...

按之前說的,順便把selinux也安裝上。

安裝docker

安裝docker沒有太大問題,需要先把docker-py安裝上。用pip安裝,所以docker模塊只能在python3環境上運行、
之前已經把selinux也裝上。這樣除了yum,其他都可以在python3環境上運行了。

---
- hosts: host1
  vars:
      ansible_tag: v1  # 指定ansible鏡像的tag(版本號)
  tasks:
    - name: 下載阿里的安裝源
      get_url:
        url: http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
        dest: /etc/yum.repos.d/
    - name: yum 安裝 docker-ce 指定版本
      yum:
        name: docker-ce-18.06.0.ce-3.el7
        state: present
    - name: 啓動docker,開機啓動
      service: name=docker.service state=started enabled=yes
    - name: 檢查docker配置文件是否存在
      file:
        path: /etc/docker/daemon.json
        state: file
      ignore_errors: True
      register: result

    - name: 如果docker配置文件不存在,則創建初始文件設置鏡像加速器
      when: result is failed
      block: 
      - name: 文件寫入內容,設置設置鏡像加速器
        lineinfile:
          path: /etc/docker/daemon.json
          create: yes
          line: |
            {
                "registry-mirrors": ["http://hub-mirror.c.163.com", "https://docker.mirrors.ustc.edu.cn"]
            }
      - name: 重啓docker,使鏡像加速器生效
        service: name=docker.service state=restarted

    - name: 安裝管理docker的模塊,檢查鏡像是否存在
      vars: {ansible_python_interpreter: /usr/bin/python3}
      block:
      - name: pip 安裝 docker-py selinux
        pip: 
          name: [docker-py, selinux]
          state: present
      - name: 檢查 Ansible 鏡像是否存在
        vars: {ansible_python_interpreter: /usr/bin/python3}
        docker_image:
          name: ansible
          tag: "{{ ansible_tag }}"
          source: local
        ignore_errors: True
        register: result

    - name: 製作 Ansible 鏡像
      vars: {ansible_python_interpreter: /usr/bin/python3}
      when: result is failed
      block:
      - name: 將 Dockerfile copy 到目標主機
        # copy 需要 selinux,所以上面用 pip 安裝了 python3 的版本
        copy:
          src: ansible/Dockerfile
          dest: /tmp/docker_file/ansible/
      - name: docker build
        docker_image:
          name: ansible
          tag: "{{ ansible_tag }}"
          build:
            path: /tmp/docker_file/ansible/
            pull: yes
          source: build
      - name: 刪除 Dockerfile 
        file:
          path: /tmp/docker_file/
          state: absent
...

docker的配置文件是json格式的,json沒有註釋,所以不能用blockinfile模塊。可以用lineinfile模塊,也可以用copy模塊。不過copy模塊有個小問題,沒法遞歸創建目錄。而lineinfile模塊有create參數,並且也支持多行。

通過Dockerfile製作鏡像

這個是構架ansible的Dockerfile:

FROM centos:centos7
RUN curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo && \
    yum -y install python36 python36-setuptools python36-pip && \
    yum clean all && \
    pip3 install pip --upgrade -i https://mirrors.aliyun.com/pypi/simple/ && \
    /usr/local/bin/pip3 install ansible -i https://mirrors.aliyun.com/pypi/simple/ && \
    mkdir /opt/ansible_project/etc/ansible -p && \
    ln -s /opt/ansible_project/etc/ansible/ /etc/ansible
VOLUME /opt/ansible_project
CMD ["ansible", "--version"]

創建鏡像查找的Dockerfile所在的文件夾是在目標主機上的。所以需要先用copy模塊將文件夾複製過去。
copy模塊使用相對目錄時,源文件的起始目錄是playbook所在的目錄,目標的起始目錄有目標主機決定(Centos的系統,默認登錄後就在用戶的家目錄)。

賬號和SSH安全優化

主要做了2件事:

  • 開啓一個管理員賬號
  • 修改默認ssh使用的端口號

playbook如下:

---
- hosts: host1
  vars:
      ansible_python_interpreter: /usr/bin/python2  # 指定使用python2,防止有坑
      init_ssh_user: admin   # 是否創建用戶,並且會禁用root密碼登錄
      init_ssh_port: 2849    # 是否修改默認ssh端口
  tasks:
    # 創建管理員用戶,禁用root登錄
    - block:
      - name: 創建用戶 {{ init_ssh_user }}
        user:
          name: "{{ init_ssh_user }}"
          # 這個是加鹽的對稱加密,所以可以反解,官網有生成密碼的方式:
          # https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module
          password: "$6${{ my_secret_salt }}$CSQSsXSoAkwtWDzjlReQ3u0jy1YyxBLouwe403dCVe.ystdi9JQvjSxhoTpYwNoT5nprsJV/UpYb9Ktj.7jLX/"
          groups: wheel
          state: present
      - name: 修改 /etc/ssh/sshd_config 禁止root登錄
        lineinfile:
          dest: /etc/ssh/sshd_config
          state: present
          regexp: "^#?PermitRootLogin"
          line: "PermitRootLogin no"
        notify: systemctl restart sshd.service
      when: init_ssh_user is defined

    # 修改ssh端口
    - block:
      - name: 防火牆開啓端口 {{ init_ssh_port }}
        firewalld:
          port: "{{ init_ssh_port }}/tcp"
          state: enabled
          permanent: yes
          immediate: yes
      - name: yum安裝 policycoreutils-python
        yum: { name: policycoreutils-python, state: present}
      - name: 設置selinux:`semanage port -a -t ssh_port_t -p tcp {{ init_ssh_port }}`
        seport:
          ports: "{{ init_ssh_port }}"
          proto: tcp
          setype: ssh_port_t
          state : present
      - name: 修改 /etc/ssh/sshd_config 的端口設置
        lineinfile:
          dest: /etc/ssh/sshd_config
          state: present
          regexp: "^#?Port"
          line: "Port {{ init_ssh_port }}"
        notify: systemctl restart sshd.service
      when: init_ssh_port is defined

  handlers:
    - name: systemctl restart sshd.service
      service: name=sshd.service state=restarted
...

驗證

去目標主機上驗證:

# docker run --rm ansible:v1
ansible 2.8.4
  config file = None
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.8 (default, Apr 25 2019, 21:02:35) [GCc 4.8.5 20150623 (Red Hat 4.8.5-36)]
# 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章