Ansible 使用 lineinfile 模块修改配置文件

需要用 Ansible 修改配置文件,其实就是在某个文件末尾添加几行内容。直观地想,直接用 shell 模块,echo>> 就完事了。
但仔细一琢磨,很可能会引发一些意想不到的问题,比如:

  • 如果需要添加的配置已经存在,echo 仍会向配置文件底部添加同样的内容
  • 如果添加配置的任务重复执行多次,则配置文件中也会多次出现重复的内容。无法做到幂等
  • 如何做到,当对应的配置已经存在,则将该配置改为期望的值;当对应的配置不存在,不做任何操作(有就修改,没有就不动。好像可以用 sed
  • 如何安全地移除指定的配置项

诸如此类。运维工作常常要关系到生产环境。任何无法预期的效果都可能产生严重的影响。而单纯使用 echo>> 向配置文件中添加内容,具有很大的不确定性。
当然可以形成一个 Shell 脚本,对各种边界进行足够的检查和判定,但这会导致代码量变大,结构复杂难以标准化;同时也容易出现遗漏的情况。

实际上 Ansible 内置的 lineinfile 就是专门用来处理上述任务的模块。

比如针对如下内容的配置文件 test_conf.ini

FIRST=true
SECOND=2

需要添加一行配置 THIRD=3

可以运行如下内容的 playbook change_config.yml

- name: change configuration
  gather_facts: false
  hosts: localhost

  tasks:
    - name: change content in test_conf.ini
      lineinfile:
        path: /home/starky/projects/ansible/practice/test_conf.ini
        regexp: '^THIRD'
        line: THIRD=3

其中 lineinfile 模块的 path 参数用于指定目标配置文件的路径;regexp 参数则用于指定对文件内容进行匹配时使用的正则表达式;最后的 line 参数表示希望在目标文件中出现的内容。

具体的步骤为:

  • 检查 line 对应的内容是否存在于 path 对应的目标文件中
  • 若已经存在。则目标文件符合要求,不对该文件做任何操作
  • 若不存在。通过 regexp 指定的正则表达式对目标文件进行匹配
  • regexp 匹配到文本行,则将该行内容修改为 line 指定的内容
  • regexp 未匹配到文本行,则将 line 对应的内容作为新的一行添加到目标文件末尾

运行效果:

$ ansible-playbook change_config.yml

PLAY [change configuration] *********************************************************************************************************

TASK [change content in test_conf.ini] **********************************************************************************************
changed: [localhost]

PLAY RECAP **************************************************************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

此时 test_conf.ini 配置文件的内容被修改为:

FIRST=true
SECOND=2
THIRD=3

若再次运行 ansible-playbook change_config.yml 命令:

$ ansible-playbook change_config.yml

PLAY [change configuration] *********************************************************************************************************

TASK [change content in test_conf.ini] **********************************************************************************************
ok: [localhost]

PLAY RECAP **************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

可以看到修改配置文件的任务执行结果为 ok,而不同于上一次的 changed。这表示 lineinfile 模块对配置文件的内容进行了检查,发现需要添加的配置行已经存在,因此未做任何改动。符合幂等的原则。

假如将配置文件中的 THIRD=3 改为 THIRD=false,再次运行 playbook:

$ ansible-playbook change_config.yml

PLAY [change configuration] *********************************************************************************************************

TASK [change content in test_conf.ini] **********************************************************************************************
changed: [localhost]

PLAY RECAP **************************************************************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

正则表达式 ^THIRD 会匹配到配置文件的第三行 THIRD=false,再将该行内容替换为 THIRD=3

最终仍可以得到我们想要的内容:

FIRST=true
SECOND=2
THIRD=3

对于 Ansible playbook 而言,我们只需要关注期望达到的状态,而不用纠结为了达到该状态需要执行哪些步骤
lineinfile 模块,line 指定的内容即为我们期望目标文件达到的状态。即该文件最终一定会包含一行与 line 相同的文本。
不管该行内容是本就已经存在的,还是通过修改 regexp 匹配到的文本行得到的,还是直接在目标文件末尾新增的。而我们只需要定义 pathregexpline 三个参数即可。

其他用法

backrefs

lineinfile 默认的行为是若 line 指定的内容未存在,regexp 正则表达式也没有任何匹配,就在文件末尾添加一行 line 指定的内容。
backrefs 参数可以修改此行为。当 backrefs 设定为 true 时,若 line 指定的内容不存在,正则表达式也没有匹配。则不做任何操作。

比如如下 playbook:

- name: change configuration
  gather_facts: false
  hosts: localhost

  tasks:
    - name: change content in test_conf.ini
      lineinfile:
        path: /home/starky/projects/ansible/practice/test_conf.ini
        regexp: '^THIRD'
        line: 'THIRD=3'
        backrefs: true

当目标文件的内容如下:

FIRST=true
SECOND=2

playbook 实际不会对其做任何修改,不会在文件末尾添加 THIRD=3。只有当文件中存在如 THIRD=false 这类内容时,playbook 才会完成匹配并替换对应的行。

没有 backrefs 表示匹配就替换,不匹配就在文件末尾添加;有 backrefs 表示匹配就替换,不匹配就不动。

删除一行内容
- name: change configuration
  gather_facts: false
  hosts: localhost

  tasks:
    - name: change content in test_conf.ini
      lineinfile:
        path: /home/starky/projects/ansible/practice/test_conf.ini
        regexp: '^THIRD'
        state: absent
在匹配行前/后添加
- name: change configuration
  gather_facts: false
  hosts: localhost

  tasks:
    - name: change content in test_conf.ini
      lineinfile:
        path: /home/starky/projects/ansible/practice/test_conf.ini
        insertbefore: '^FIRST'
        line: 'ZERO=false'
- name: change configuration
  gather_facts: false
  hosts: localhost

  tasks:
    - name: change content in test_conf.ini
      lineinfile:
        path: /home/starky/projects/ansible/practice/test_conf.ini
        insertafter: '^THIRD'
        line: 'FOURTH=4'

需要注意以下两点:

  • line 指定的内容已经存在于目标文件中时,不管其具体在什么位置,目标文件都不会做任何修改
  • insertbeforeinsertafter 指定的正则表达式没有任何匹配时,都会在文件末尾添加 line 指定的内容

官网示例

# NOTE: Before 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
- name: Ensure SELinux is set to enforcing mode
  ansible.builtin.lineinfile:
    path: /etc/selinux/config
    regexp: '^SELINUX='
    line: SELINUX=enforcing

- name: Make sure group wheel is not in the sudoers configuration
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    state: absent
    regexp: '^%wheel'

- name: Replace a localhost entry with our own
  ansible.builtin.lineinfile:
    path: /etc/hosts
    regexp: '^127\.0\.0\.1'
    line: 127.0.0.1 localhost
    owner: root
    group: root
    mode: '0644'

- name: Replace a localhost entry searching for a literal string to avoid escaping
  lineinfile:
    path: /etc/hosts
    search_string: '127.0.0.1'
    line: 127.0.0.1 localhost
    owner: root
    group: root
    mode: '0644'

- name: Ensure the default Apache port is 8080
  ansible.builtin.lineinfile:
    path: /etc/httpd/conf/httpd.conf
    regexp: '^Listen '
    insertafter: '^#Listen '
    line: Listen 8080

- name: Ensure php extension matches new pattern
  lineinfile:
    path: /etc/httpd/conf/httpd.conf
    search_string: '<FilesMatch ".php[45]?$">'
    insertafter: '^\t<Location \/>\n'
    line: '        <FilesMatch ".php[34]?$">'

- name: Ensure we have our own comment added to /etc/services
  ansible.builtin.lineinfile:
    path: /etc/services
    regexp: '^# port for http'
    insertbefore: '^www.*80/tcp'
    line: '# port for http by default'

- name: Add a line to a file if the file does not exist, without passing regexp
  ansible.builtin.lineinfile:
    path: /tmp/testfile
    line: 192.168.1.99 foo.lab.net foo
    create: yes

# NOTE: Yaml requires escaping backslashes in double quotes but not in single quotes
- name: Ensure the JBoss memory settings are exactly as needed
  ansible.builtin.lineinfile:
    path: /opt/jboss-as/bin/standalone.conf
    regexp: '^(.*)Xms(\d+)m(.*)$'
    line: '\1Xms${xms}m\3'
    backrefs: yes

# NOTE: Fully quoted because of the ': ' on the line. See the Gotchas in the YAML docs.
- name: Validate the sudoers file before saving
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    state: present
    regexp: '^%ADMIN ALL='
    line: '%ADMIN ALL=(ALL) NOPASSWD: ALL'
    validate: /usr/sbin/visudo -cf %s

# See https://docs.python.org/3/library/re.html for further details on syntax
- name: Use backrefs with alternative group syntax to avoid conflicts with variable values
  ansible.builtin.lineinfile:
    path: /tmp/config
    regexp: ^(host=).*
    line: \g<1>{{ hostname }}
    backrefs: yes

参考资料

ansible.builtin.lineinfile – Manage lines in text files

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容