作为一名 OCM 认证的 DBA,我在职业生涯中部署过上百套 Oracle 数据库。从最初照着 MOS 文档一步步敲命令,到如今用 Ansible 一键完成从 OS 准备到建库的全流程,这个转变让我深刻体会到 Infrastructure as Code (IaC) 的力量。本文将完整记录如何用 Ansible Role 实现 Oracle 数据库的自动化部署,代码可直接用于生产环境。
一、问题背景 1.1 传统手工部署的痛点 每个 DBA 都经历过这样的场景:拿到一台新服务器,开始 Oracle 部署——配置内核参数、安装依赖包、创建用户、设置环境变量、运行 runInstaller、执行 root.sh、DBCA 建库、配置监听……整个流程下来,顺利的话需要 2-3 小时,遇到问题可能折腾一整天。
手工部署的核心问题有三个:
耗时长 :每个步骤都需要手动执行和等待,无法并行化
易出错 :漏装一个 libaio-devel,少配一个 shmmax 参数,都可能导致安装失败
不可重复 :这次部署成功了,下次换个环境又要从头摸索
1.2 DevOps 时代对 DBA 的新要求 现代企业要求 DBA 不仅会管理数据库,还要具备 Infrastructure as Code 的能力。所有环境配置必须版本化、可审计、可追溯。开发团队要求”给我一套和生产一样的测试库”时,你需要在 30 分钟内交付,而不是 3 天。
1.3 Oracle 部署自动化的价值 通过 Ansible 实现 Oracle 部署自动化后,我们获得了:
标准化 :每套环境配置完全一致,消除”雪花服务器”
版本化 :所有配置通过 Git 管理,变更可追溯
审计合规 :Playbook 即文档,部署过程透明可审计
快速交付 :新环境部署从 3 小时缩短到 40 分钟
二、理论分析 2.1 自动化工具选型
工具
架构
学习曲线
适合场景
Ansible
无Agent,SSH推送
低
配置管理、应用部署
Puppet
Agent/Master
中
大规模基础设施管理
Chef
Agent/Master
高
复杂编排、Ruby生态
Terraform
无Agent,API调用
中
云资源编排、基础设施供给
为什么 Ansible 更适合 Oracle 部署?
无 Agent :不需要在目标服务器安装额外软件,Oracle 服务器越”干净”越好
YAML 易读 :Playbook 即文档,DBA 不需要学 Ruby 或 DSL
SSH 推送 :利用现有 SSH 通道,无需额外端口和安全审批
丰富的 Module :yum、template、user、sysctl 等模块开箱即用
幂等性 :重复执行不会破坏已有状态,适合日常巡检和修复
2.2 Ansible 核心概念 在开始编写 Playbook 之前,需要理解以下核心概念:
Inventory(清单) :定义被管理的主机和分组,是 Ansible 操作的目标范围。
1 2 3 4 5 6 7 8 9 all: children: oracle_servers: hosts: db01: ansible_host: 192.168 .1 .101 db02: ansible_host: 192.168 .1 .102
Playbook(剧本) :以 YAML 格式编写的任务编排文件,定义”在哪些主机上执行哪些任务”。
Role(角色) :Playbook 的组织方式,将 tasks、handlers、templates、vars 等按照标准目录结构打包,便于复用和共享。
Module(模块) :Ansible 的执行单元,每个模块完成一个特定功能,如 yum 安装软件包、template 渲染模板文件。
Jinja2 模板引擎 :Ansible 使用 Jinja2 作为模板引擎,可以在配置文件中插入变量、使用条件判断和循环。
Handler 与 Tag :Handler 是被通知触发的任务,通常用于重启服务;Tag 用于给任务打标签,执行时可选择性运行特定标签的任务。
2.3 Oracle 部署自动化的设计思路 Oracle 部署是一个多阶段的复杂过程,我们需要将其拆解为清晰的阶段:
1 OS准备 → 用户/目录创建 → 软件安装 → 建库 → 监听配置 → 参数调优
每个阶段对应一个 Task 文件,通过 Role 统一组织。关键设计原则:
幂等性设计 :每个任务都要考虑”如果已经执行过怎么办”。例如,创建用户前先检查用户是否存在,安装软件包时利用 state: present 确保不重复安装。
错误处理 :对于关键步骤(如 runInstaller),使用 register 捕获返回值,结合 failed_when 判断成功条件。失败时记录日志并通知 Handler 清理现场。
回滚策略 :Ansible 天生不擅长回滚,我们通过”先备份再修改”的方式变通实现。关键文件修改前使用 copy 模块备份,失败时从备份恢复。
三、实战操作 3.1 Ansible Role 结构设计 完整的 Oracle 部署 Role 目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 roles/oracle/ ├── defaults/ │ └── main.yml # 默认变量(优先级最低) ├── vars/ │ └── main.yml # 角色变量(优先级高) ├── tasks/ │ ├── main.yml # 主入口,include其他task │ ├── os_prepare.yml # OS准备 │ ├── user_setup.yml # 用户/目录创建 │ ├── install.yml # 软件安装 │ ├── create_db.yml # 建库 │ └── listener.yml # 监听配置 ├── templates/ │ ├── dbora.service.j2 # systemd服务模板 │ ├── oracle_env.sh.j2 # 环境变量模板 │ ├── db_install.rsp.j2 # 安装响应文件模板 │ ├── dbca.rsp.j2 # 建库响应文件模板 │ └── listener.ora.j2 # 监听配置模板 ├── handlers/ │ └── main.yml # Handler定义 ├── files/ │ └── limits_oracle.conf # limits配置文件 └── meta/ └── main.yml # Role元数据
3.2 OS 准备 Playbook defaults/main.yml — 默认变量定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 --- oracle_version: "19c" oracle_home: "/u01/app/oracle/product/19.0.0/dbhome_1" oracle_base: "/u01/app/oracle" oracle_inventory: "/u01/app/oraInventory" oracle_sid: "ORCL" oracle_characterset: "AL32UTF8" oracle_memory_percent: 70 oracle_user: "oracle" oracle_group: "oinstall" oracle_dba_group: "dba" oracle_oper_group: "oper" oracle_backup_group: "backupdba" oracle_dg_group: "dgdba" oracle_km_group: "kmdba" oracle_shmmax: 8589934592 oracle_shmall: 2097152 oracle_sem: "250 32000 100 128" oracle_software_path: "/tmp/oracle_software" oracle_install_file: "LINUX.X64_193000_db_home.zip" oracle_listener_port: 1521 oracle_sys_password: "Oracle#2026" oracle_system_password: "Oracle#2026"
tasks/os_prepare.yml — OS 准备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 --- - name: Install required packages for Oracle yum: name: - binutils - compat-libcap1 - compat-libstdc++-33 - gcc - gcc-c++ - glibc - glibc-devel - ksh - libaio - libaio-devel - libgcc - libstdc++ - libstdc++-devel - libnsl - libXext - libXtst - libX11 - libXau - libxcb - libXi - make - sysstat - unzip - bc - flex - net-tools - smartmontools state: present tags: [os_prepare , packages ] - name: Configure kernel parameters sysctl: name: "{{ item.name }} " value: "{{ item.value }} " state: present reload: yes sysctl_file: /etc/sysctl.d/99-oracle.conf loop: - { name: "kernel.shmmax" , value: "{{ oracle_shmmax }} " } - { name: "kernel.shmall" , value: "{{ oracle_shmall }} " } - { name: "kernel.shmmni" , value: "4096" } - { name: "kernel.sem" , value: "{{ oracle_sem }} " } - { name: "fs.file-max" , value: "6815744" } - { name: "fs.aio-max-nr" , value: "1048576" } - { name: "net.ipv4.ip_local_port_range" , value: "9000 65500" } - { name: "net.core.rmem_default" , value: "262144" } - { name: "net.core.rmem_max" , value: "4194304" } - { name: "net.core.wmem_default" , value: "262144" } - { name: "net.core.wmem_max" , value: "1048576" } - { name: "vm.swappiness" , value: "10" } - { name: "vm.dirty_ratio" , value: "60" } tags: [os_prepare , kernel ] - name: Configure limits for Oracle user copy: src: limits_oracle.conf dest: /etc/security/limits.d/99-oracle.conf owner: root group: root mode: "0644" tags: [os_prepare , limits ] - name: Disable Transparent Huge Pages lineinfile: path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX=' line: 'GRUB_CMDLINE_LINUX="transparent_hugepage=never {{ ansible_cmdline | default({}) | dict2items | selectattr("key", "ne", "transparent_hugepage") | map(attribute="value") | join(" ") }}"' notify: Regenerate grub config tags: [os_prepare , thp ] - name: Configure /etc/hosts lineinfile: path: /etc/hosts line: "{{ ansible_default_ipv4.address }} {{ ansible_hostname }} {{ ansible_fqdn }} " state: present tags: [os_prepare , hosts ]
files/limits_oracle.conf 1 2 3 4 5 6 7 8 oracle soft nofile 1024 oracle hard nofile 65536 oracle soft nproc 16384 oracle hard nproc 16384 oracle soft stack 10240 oracle hard stack 32768 oracle soft memlock unlimited oracle hard memlock unlimited
3.3 用户与目录创建 tasks/user_setup.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 --- - name: Create Oracle groups group: name: "{{ item }} " state: present loop: - "{{ oracle_group }} " - "{{ oracle_dba_group }} " - "{{ oracle_oper_group }} " - "{{ oracle_backup_group }} " - "{{ oracle_dg_group }} " - "{{ oracle_km_group }} " tags: [user_setup , groups ] - name: Create Oracle user user: name: "{{ oracle_user }} " group: "{{ oracle_group }} " groups: - "{{ oracle_dba_group }} " - "{{ oracle_oper_group }} " - "{{ oracle_backup_group }} " - "{{ oracle_dg_group }} " - "{{ oracle_km_group }} " home: "/home/{{ oracle_user }} " shell: /bin/bash create_home: yes state: present tags: [user_setup , users ] - name: Create Oracle directory structure file: path: "{{ item }} " state: directory owner: "{{ oracle_user }} " group: "{{ oracle_group }} " mode: "0755" loop: - "{{ oracle_base }} " - "{{ oracle_home }} " - "{{ oracle_inventory }} " - "{{ oracle_base }} /admin" - "{{ oracle_base }} /admin/{{ oracle_sid }} /adump" - "{{ oracle_base }} /oradata" - "{{ oracle_base }} /fast_recovery_area" - "{{ oracle_base }} /oradata/{{ oracle_sid }} " - "{{ oracle_software_path }} " tags: [user_setup , directories ] - name: Deploy Oracle environment profile template: src: oracle_env.sh.j2 dest: "/home/{{ oracle_user }} /.bash_profile" owner: "{{ oracle_user }} " group: "{{ oracle_group }} " mode: "0644" backup: yes tags: [user_setup , profile ]
templates/oracle_env.sh.j2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 export ORACLE_BASE={{ oracle_base }}export ORACLE_HOME={{ oracle_home }}export ORACLE_SID={{ oracle_sid }}export ORACLE_UNQNAME={{ oracle_sid }}export PATH=$ORACLE_HOME /bin:$PATH export LD_LIBRARY_PATH=$ORACLE_HOME /lib:$LD_LIBRARY_PATH export NLS_LANG=AMERICAN_AMERICA.AL32UTF8export NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" export TNS_ADMIN=$ORACLE_HOME /network/adminexport HISTSIZE=10000export HISTFILESIZE=20000export HISTCONTROL=ignoredupsexport SQLPATH=$ORACLE_HOME /sqlplus/adminset -o vi
3.4 软件安装 Playbook tasks/install.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 --- - name: Check if Oracle software is already installed stat: path: "{{ oracle_home }} /bin/oracle" register: oracle_installed tags: [install ] - name: Unzip Oracle software unarchive: src: "{{ oracle_software_path }} /{{ oracle_install_file }} " dest: "{{ oracle_home }} " remote_src: yes creates: "{{ oracle_home }} /runInstaller" when: not oracle_installed.stat.exists become: yes become_user: "{{ oracle_user }} " tags: [install ] - name: Deploy db_install.rsp response file template: src: db_install.rsp.j2 dest: "{{ oracle_software_path }} /db_install.rsp" owner: "{{ oracle_user }} " group: "{{ oracle_group }} " mode: "0600" when: not oracle_installed.stat.exists tags: [install , response ] - name: Run Oracle installer in silent mode command: > {{ oracle_home }}/runInstaller -silent -noconfig -waitforcompletion -ignoreSysPrereqs -responseFile {{ oracle_software_path }}/db_install.rsp become: yes become_user: "{{ oracle_user }} " when: not oracle_installed.stat.exists register: install_result timeout: 1800 tags: [install , runinstaller ] - name: Execute root.sh command: "{{ oracle_home }} /root.sh" become: yes when: not oracle_installed.stat.exists register: rootsh_result tags: [install , rootsh ] - name: Verify Oracle installation command: "{{ oracle_home }} /bin/sqlplus -V" become: yes become_user: "{{ oracle_user }} " register: sqlplus_version changed_when: false failed_when: sqlplus_version.rc != 0 tags: [install , verify ]
templates/db_install.rsp.j2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 oracle.install.responseFileVersion =/oracle/install/rspfmt_dbinstall_response_schema_v19.0.0 oracle.install.option =INSTALL_DB_SWONLYUNIX_GROUP_NAME ={{ oracle_group }}INVENTORY_LOCATION ={{ oracle_inventory }}ORACLE_HOME ={{ oracle_home }}ORACLE_BASE ={{ oracle_base }}oracle.install.db.InstallEdition =EEoracle.install.db.OSDBA_GROUP ={{ oracle_dba_group }}oracle.install.db.OSOPER_GROUP ={{ oracle_oper_group }}oracle.install.db.OSBACKUPDBA_GROUP ={{ oracle_backup_group }}oracle.install.db.OSDGDBA_GROUP ={{ oracle_dg_group }}oracle.install.db.OSKMDBA_GROUP ={{ oracle_km_group }}oracle.install.db.OSRACDBA_GROUP ={{ oracle_dba_group }}SECURITY_UPDATES_VIA_MYORACLESUPPORT =false DECLINE_SECURITY_UPDATES =true oracle.install.db.ConfigureAsContainerDB =false
3.5 建库 Playbook tasks/create_db.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 --- - name: Check if database already exists shell: | source /home/{{ oracle_user }}/.bash_profile sqlplus -s / as sysdba <<'EOF' SET HEADING OFF FEEDBACK OFF SELECT COUNT(*) FROM v$database WHERE name='{{ oracle_sid }}'; EXIT; EOF become: yes become_user: "{{ oracle_user }} " register: db_exists changed_when: false failed_when: false tags: [create_db ] - name: Deploy DBCA response file template: src: dbca.rsp.j2 dest: "{{ oracle_software_path }} /dbca_{{ oracle_sid }} .rsp" owner: "{{ oracle_user }} " group: "{{ oracle_group }} " mode: "0600" when: "'0' in db_exists.stdout" tags: [create_db , response ] - name: Create database with DBCA command: > {{ oracle_home }}/bin/dbca -silent -createDatabase -responseFile {{ oracle_software_path }}/dbca_{{ oracle_sid }}.rsp become: yes become_user: "{{ oracle_user }} " when: "'0' in db_exists.stdout" register: dbca_result timeout: 3600 tags: [create_db , dbca ] - name: Verify database is open shell: | source /home/{{ oracle_user }}/.bash_profile sqlplus -s / as sysdba <<'EOF' SET HEADING OFF FEEDBACK OFF SELECT STATUS FROM V$INSTANCE; EXIT; EOF become: yes become_user: "{{ oracle_user }} " register: db_status changed_when: false failed_when: "'OPEN' not in db_status.stdout" tags: [create_db , verify ]
templates/dbca.rsp.j2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 responseFileVersion =/oracle/assistants/rspfmt_dbca_response_schema_v19.0.0 gdbName ={{ oracle_sid }}sid ={{ oracle_sid }}databaseConfigType =SIcreateAsContainerDatabase =false templateName =General_Purpose.dbcsysPassword ={{ oracle_sys_password }}systemPassword ={{ oracle_system_password }}datafileDestination ={{ oracle_base }}/oradatarecoveryAreaDestination ={{ oracle_base }}/fast_recovery_arearecoveryAreaSize =10240 storageType =FScharacterSet ={{ oracle_characterset }}nationalCharacterSet =AL16UTF16totalMemory ={{ (ansible_memtotal_mb * oracle_memory_percent / 100 ) | int }}automaticMemoryManagement =FALSE emConfiguration =NONE
3.6 监听配置与参数调优 tasks/listener.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 --- - name: Check if listener exists stat: path: "{{ oracle_home }} /network/admin/listener.ora" register: listener_exists tags: [listener ] - name: Create listener using NETCA command: > {{ oracle_home }}/bin/netca /silent /responseFile {{ oracle_home }}/assistants/netca/netca.rsp become: yes become_user: "{{ oracle_user }} " when: not listener_exists.stat.exists register: netca_result tags: [listener , netca ] - name: Deploy optimized listener.ora template: src: listener.ora.j2 dest: "{{ oracle_home }} /network/admin/listener.ora" owner: "{{ oracle_user }} " group: "{{ oracle_group }} " mode: "0644" backup: yes notify: Restart listener tags: [listener , config ] - name: Deploy tnsnames.ora template: src: tnsnames.ora.j2 dest: "{{ oracle_home }} /network/admin/tnsnames.ora" owner: "{{ oracle_user }} " group: "{{ oracle_group }} " mode: "0644" tags: [listener , tns ]
templates/listener.ora.j2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = {{ ansible_hostname }})(PORT = {{ oracle_listener_port }})) (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521)) ) ) SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (GLOBAL_DBNAME = {{ oracle_sid }}) (ORACLE_HOME = {{ oracle_home }}) (SID_NAME = {{ oracle_sid }}) ) ) INBOUND_CONNECT_TIMEOUT_LISTENER = 10 CONNECT_TIMEOUT_LISTENER = 10 LOG_LEVEL_LISTENER = ADMINDIAG_ADR_ENABLED_LISTENER = ON ADR_BASE_LISTENER = {{ oracle_base }}
3.7 主入口与 Handler tasks/main.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 --- - import_tasks: os_prepare.yml tags: [os_prepare ] - import_tasks: user_setup.yml tags: [user_setup ] - import_tasks: install.yml tags: [install ] - import_tasks: create_db.yml tags: [create_db ] - import_tasks: listener.yml tags: [listener ] - name: Deploy Oracle systemd service template: src: dbora.service.j2 dest: /etc/systemd/system/dbora.service owner: root group: root mode: "0644" notify: - Reload systemd - Enable dbora service tags: [systemd ]
handlers/main.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- - name: Regenerate grub config command: grub2-mkconfig -o /boot/grub2/grub.cfg listen: "Regenerate grub config" - name: Reload systemd systemd: daemon_reload: yes - name: Enable dbora service systemd: name: dbora enabled: yes - name: Restart listener command: "{{ oracle_home }} /bin/lsnrctl restart" become: yes become_user: "{{ oracle_user }} " listen: "Restart listener"
3.8 主 Playbook 与 Inventory playbook.yml(项目根目录) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 --- - name: Deploy Oracle Database hosts: oracle_servers become: yes gather_facts: yes pre_tasks: - name: Validate target OS assert: that: - ansible_os_family == "RedHat" - ansible_distribution_major_version | int >= 7 fail_msg: "此 Role 仅支持 RHEL/CentOS 7+ 系统" - name: Check minimum memory assert: that: - ansible_memtotal_mb >= 4096 fail_msg: "Oracle 19c 最低要求 4GB 内存" roles: - role: oracle
inventory/hosts.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 --- all: children: oracle_servers: hosts: prod-db01: ansible_host: 10.0 .1 .101 oracle_sid: PROD01 oracle_memory_percent: 70 prod-db02: ansible_host: 10.0 .1 .102 oracle_sid: PROD02 oracle_memory_percent: 60 vars: ansible_user: deploy ansible_become_method: sudo ansible_python_interpreter: /usr/bin/python3
四、结果验证 4.1 Ansible Dry-Run 在真正执行部署之前,务必使用 --check 模式进行试运行:
1 2 3 4 5 6 7 8 9 10 11 ansible-playbook playbook.yml --syntax-check ansible-playbook -i inventory/hosts.yml playbook.yml --check --diff ansible-playbook -i inventory/hosts.yml playbook.yml --tags "os_prepare" --check ansible-playbook -i inventory/hosts.yml playbook.yml --skip-tags "create_db"
4.2 安装后验证脚本 部署完成后,运行验证 Playbook 确认所有组件状态正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 --- - name: Verify Oracle Installation hosts: oracle_servers become: yes tasks: - name: Check Oracle binary stat: path: "{{ oracle_home }} /bin/oracle" register: oracle_bin - name: Check SQL*Plus version command: "{{ oracle_home }} /bin/sqlplus -V" become_user: "{{ oracle_user }} " register: sqlplus_ver changed_when: false - name: Check listener status command: "{{ oracle_home }} /bin/lsnrctl status" become_user: "{{ oracle_user }} " register: lsnr_status changed_when: false - name: Check database status shell: | source /home/{{ oracle_user }}/.bash_profile sqlplus -s / as sysdba <<'EOF' SET HEADING OFF FEEDBACK OFF PAGESIZE 0 SELECT 'DB_STATUS:' || STATUS || ' OPEN_MODE:' || OPEN_MODE FROM V$INSTANCE, V$DATABASE; EXIT; EOF become_user: "{{ oracle_user }} " register: db_status changed_when: false - name: Print verification results debug: msg: | ========== Oracle 部署验证结果 ========== Oracle Binary: {{ 'OK' if oracle_bin.stat.exists else 'MISSING' }} SQL*Plus: {{ sqlplus_ver.stdout }} Listener: {{ 'RUNNING' if 'running' in lsnr_status.stdout else 'NOT RUNNING' }} Database: {{ db_status.stdout_lines | join('') }} ==========================================
4.3 幂等性测试 幂等性是 Ansible 的核心价值之一。验证方式很简单——执行两次,第二次不应该有任何 changed 任务:
1 2 3 4 5 6 ansible-playbook -i inventory/hosts.yml playbook.yml ansible-playbook -i inventory/hosts.yml playbook.yml
如果第二次仍有任务标记为 changed,说明该任务没有正确实现幂等性,需要检查 when 条件或使用 creates、stat 等方式完善。
五、经验总结 5.1 最佳实践 经过多次生产部署,总结以下经验:
分阶段执行 :不要一次性运行全部任务,先用 --tags 分阶段验证,确保每个阶段都正确后再全流程执行
备份优先 :所有涉及配置文件修改的操作,必须开启 backup: yes
日志归档 :每次执行 Ansible 输出都重定向到文件,便于审计和排错
环境隔离 :dev/staging/prod 使用不同的 Inventory 文件,变量通过 group_vars 分层管理
版本矩阵 :在 defaults/main.yml 中维护 Oracle 版本和补丁级别的映射关系,便于升级
5.2 安全考虑 Oracle 数据库密码不应该明文存储在 Inventory 或变量文件中。使用 Ansible Vault 加密敏感信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ansible-vault create group_vars/all/vault.yml oracle_sys_password: "{{ vault_oracle_sys_password }}" ansible-playbook playbook.yml --ask-vault-pass ansible-playbook playbook.yml --vault-password-file ~/.vault_pass
5.3 版本管理与协作 将整个 Ansible 项目纳入 Git 管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 oracle-deploy/ ├── ansible.cfg ├── playbook.yml ├── verify.yml ├── inventory/ │ ├── dev/ │ │ └── hosts.yml │ ├── staging/ │ │ └── hosts.yml │ └── prod/ │ └── hosts.yml ├── group_vars/ │ ├── all/ │ │ └── vault.yml │ └── oracle_servers/ │ └── main.yml ├── roles/ │ └── oracle/ └── README.md
在团队协作场景下,建议部署 Ansible Tower 或开源的 AWX ,提供 Web UI、RBAC、Job Scheduling、凭证管理等企业级能力。
5.4 与 CI/CD 集成 将 Oracle 部署纳入 CI/CD 流水线的典型架构:
1 2 3 4 5 Git Push → Jenkins/GitLab CI → AWX → 目标服务器 │ │ ├─ Lint (yamllint) ├─ OS Prepare ├─ Syntax Check ├─ Install Oracle └─ Molecule Test └─ Create DB
Molecule 可以用于 Ansible Role 的自动化测试,在容器中模拟部署过程,验证 Role 的正确性和幂等性。
自动化部署不是一蹴而就的事情,它需要不断的迭代和优化。但一旦你把 Oracle 部署流程用 Ansible 代码化之后,你会发现:新环境交付不再是噩梦,而是 ansible-playbook 的一次优雅执行。作为 OCM 认证的 DBA,掌握 IaC 能力是这个时代的基本功。
本文涉及的所有 Role 代码已在生产环境验证通过,适用于 Oracle 19c 部署。如有问题或改进建议,欢迎在评论区交流。