DNS&&邮件服务
6439 字
32 分钟
DNS&&邮件服务

DNS&&邮件服务
[TOC]
DNS介绍
- DNS 原理:
- 域名结构:根域(.) -> 顶级域(.com, .cn) -> 次级域(baidu.com) -> 主机名(www)
- 解析过程:客户端 -> 本地 Hosts -> 缓存名称服务器(本实验重点) -> 根服务器 -> 顶级域服务器 -> 权威服务器
- 核心软件:Unbound。这是一个验证、递归和缓存 DNS 解析器
- 实验目标:配置一台 Linux 机器作为本地缓存名称服务器,能够为局域网内的其他机器提供域名解析服务,并能手动管理缓存
实验环境
| 角色 | 主机名 | IP 地址 | 软件/职责 |
|---|---|---|---|
| DNS 服务器 | Server | 10.0.0.101 | 安装 Unbound,作为缓存服务器 |
| 客户端/测试机 | Client | 10.0.0.102 | 修改 DNS 指向服务器,测试解析 |
搭建 DNS 服务器
-
安装软件
Terminal window dnf -y install unbound -
修改主配置文件 编辑
/etc/unbound/unbound.conf-
关键配置:找到或添加
interface和access-control,确保服务器监听在你的网卡 IP 上,并允许客户端网段访问interface: 10.0.0.101access-control: 10.0.0.0/24 allow -
进阶配置:如何配置“权威根域缓存”
- 你需要找到
auth-zone相关配置,或者在server:块中确保它能正常递归
- 你需要找到
-
-
启动服务
Terminal window systemctl enable --now unbound# 检查端口是否监听 (53端口)ss -lntup | grep unbound
客户端测试
-
修改 DNS 指向 将测试机的 DNS 指向你刚才搭建的服务器 IP
Terminal window # 临时修改echo "nameserver 10.0.0.101" > /etc/resolv.conf# 或者使用 nmcli (NetworkManager)nmcli connection modify internal ipv4.dns 10.0.0.101 -
进行解析测试 使用
dig命令测试域名解析,观察是否能获得结果Terminal window dig jd.com# 或者测试其他域名dig www.baidu.com
缓存管理
-
查看当前缓存
Terminal window # 导出所有缓存并查找特定域名unbound-control dump_cache | grep jd.com -
清除缓存
Terminal window # 清除特定域名unbound-control flush jd.com# 清除整个区域unbound-control flush_zone jd.com -
导入/导出缓存
Terminal window unbound-control dump_cache > dns_cache.txt # 备份unbound-control flush . # 清空unbound-control load_cache < dns_cache.txt # 恢复
搭建DNS服务器
[root@Zabbix ~]# mkdir -p /ansible/roles[root@Zabbix ~]# cd /ansible/roles[root@Zabbix roles]# ansible-galaxy init Server- Role Server was created successfully[root@Zabbix roles]# cd Server/[root@Zabbix Server]# rm -rf defaults/ meta/ tests/ README.md================================[root@Zabbix roles]# ansible-galaxy init Client- Role Client was created successfully[root@Zabbix roles]# cd Client/[root@Zabbix Client]# rm -rf defaults/ meta/ tests/ README.md- vim Server/vars/main.yml
server_name: unboundserver_ip: 10.0.0.101- vim Server/tasks/main.yml
- name: Install "{{server_name}}" server yum: name: "{{server_name}}" state: present
- name: Configure "{{server_name}}" file blockinfile: path: /usr/share/unbound/fedora-defaults.conf block: | interface: "{{server_ip}}" access-control: 10.0.0.0/24 allow marker: "# {mark} Ansible" insertafter: "^.*interface-automatic" notify: Restart "{{server_name}}"
- name: Start "{{server_name}}" systemd: name: "{{server_name}}" state: started enabled: yes
- name: Check "{{server_name}}" shell: "ss -lntup | grep unbound" register: check_re
- name: Print check_re debug: msg: "{{check_re.stdout_lines}}"- vim Server/handlers/main.yml
- name: Restart "{{server_name}}" systemd: name: "{{server_name}}" state: restarted- vim Client/tasks/main.yml
- name: Ping test command: 'ping -c1 -W2 www.baidu.com' register: ping_re
- name: Print ping_re debug: msg: "{{ping_re.stdout_lines}}"
- name: Configuer dns file lineinfile: path: /etc/resolv.conf regexp: '^nameserver' line: 'nameserver 10.0.0.101' state: present
- name: dig test command: 'dig jd.com' register: dig_re
- name: Print dig_re debug: msg: "{{dig_re.stdout_lines}}"- vim site.yml
- name: 配置 Server 主机 hosts: Server roles: - Server
- name: 配置 Client 主机 hosts: Client roles: - Client
- name: Server 测试验证 hosts: Server tasks: - name: Result test shell: 'unbound-control dump_cache | grep jd.com' register: result_end when: ansible_hostname is match "Server"
- name: Print result_end debug: msg: "{{result_end.stdout_lines}}" when: ansible_hostname is match "Server"'一切准备就绪!!!'# 恢复快照&&免密连接!![root@Zabbix ~]# ssh-copy-id -i ~/.ssh/my_key.pub 192.168.88.101[root@Zabbix ~]# ssh-copy-id -i ~/.ssh/my_key.pub 192.168.88.102=========================[root@Zabbix ~]# tree /ansible//ansible/└── roles ├── Client │ ├── files │ ├── handlers │ ├── tasks │ │ └── main.yml │ ├── templates │ └── vars ├── Server │ ├── files │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ ├── templates │ └── vars │ └── main.yml └── site.yml[root@Zabbix ~]# ansible all -m pingServer | SUCCESS => { "changed": false, "ping": "pong"}Client | SUCCESS => { "changed": false, "ping": "pong"}[root@Zabbix ~]# ansible-playbook --syntax-check /ansible/roles/site.yml
playbook: /ansible/roles/site.yml[root@Zabbix ~]# ansible-playbook /ansible/roles/site.yml _____________________< PLAY [配置 Server 主机] > --------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
__________________________________________< TASK [Server : Install "unbound" server] > ------------------------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] __________________________________________< TASK [Server : Configure "unbound" file] > ------------------------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] _________________________________< TASK [Server : Start "unbound"] > --------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] _________________________________< TASK [Server : Check "unbound"] > --------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] ________________________________< TASK [Server : Print check_re] > -------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
ok: [Server] => { "msg": [ "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=9)) ", "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=7)) ", "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=5)) ", "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=3)) ", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=10))", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=8)) ", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=6)) ", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=2994,fd=4)) " ]} ______________________________________________< RUNNING HANDLER [Server : Restart "unbound"] > ---------------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] _____________________< PLAY [配置 Client 主机] > --------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
___________________________< TASK [Client : Ping test] > --------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Client] _______________________________< TASK [Client : Print ping_re] > ------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
ok: [Client] => { "msg": [ "PING www.a.shifen.com (180.101.51.73) 56(84) bytes of data.", "64 bytes from 180.101.51.73: icmp_seq=1 ttl=128 time=29.9 ms", "", "--- www.a.shifen.com ping statistics ---", "1 packets transmitted, 1 received, 0% packet loss, time 0ms", "rtt min/avg/max/mdev = 29.884/29.884/29.884/0.000 ms" ]} ____________________________________< TASK [Client : Configuer dns file] > ------------------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Client] __________________________< TASK [Client : dig test] > -------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Client] ______________________________< TASK [Client : Print dig_re] > ------------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
ok: [Client] => { "msg": [ "", "; <<>> DiG 9.18.33 <<>> jd.com", ";; global options: +cmd", ";; Got answer:", ";; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15200", ";; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1", "", ";; OPT PSEUDOSECTION:", "; EDNS: version: 0, flags:; udp: 1232", ";; QUESTION SECTION:", ";jd.com.\t\t\t\tIN\tA", "", ";; ANSWER SECTION:", "jd.com.\t\t\t60\tIN\tA\t111.13.149.108", "jd.com.\t\t\t60\tIN\tA\t106.39.171.134", "jd.com.\t\t\t60\tIN\tA\t211.144.27.126", "jd.com.\t\t\t60\tIN\tA\t211.144.24.218", "", ";; Query time: 428 msec", ";; SERVER: 10.0.0.101#53(10.0.0.101) (UDP)", ";; WHEN: Thu Apr 09 21:15:05 CST 2026", ";; MSG SIZE rcvd: 99" ]} ____________________< PLAY [Server 测试验证] > -------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
____________________< TASK [Result test] > -------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] _________________________< TASK [Print result_end] > ------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
ok: [Server] => { "msg": [ "jdcache.com.\t720\tIN\tSOA\tns1.jdcache.com. apollo.jd.com. 2015081101 10800 3600 604800 38400", "ns5.jd.com.\t120\tIN\tA\t120.52.149.254", "jd.com.\t86400\tIN\tNS\tns3.jdcache.com.", "jd.com.\t86400\tIN\tNS\tns4.jdcache.com.", "jd.com.\t86400\tIN\tNS\tns5.jdcache.com.", "jd.com.\t86400\tIN\tNS\tns1.jd.com.", "jd.com.\t86400\tIN\tNS\tns2.jd.com.", "jd.com.\t86400\tIN\tNS\tns3.jd.com.", "jd.com.\t86400\tIN\tNS\tns4.jd.com.", "jd.com.\t86400\tIN\tNS\tns5.jd.com.", "jd.com.\t86400\tIN\tNS\tns1.jdcache.com.", "jd.com.\t86400\tIN\tNS\tns2.jdcache.com.", "jd.com.\t60\tIN\tA\t211.144.24.218", "jd.com.\t60\tIN\tA\t111.13.149.108", "jd.com.\t60\tIN\tA\t106.39.171.134", "jd.com.\t60\tIN\tA\t211.144.27.126", "jdcache.com.\t86400\tIN\tNS\tns1.jd.com.", "jdcache.com.\t86400\tIN\tNS\tns2.jd.com.", "jdcache.com.\t86400\tIN\tNS\tns3.jd.com.", "jdcache.com.\t86400\tIN\tNS\tns4.jd.com.", "jdcache.com.\t86400\tIN\tNS\tns5.jd.com.", "ns3.jd.com.\t86400\tIN\tA\t120.52.149.254", "ns4.jd.com.\t86400\tIN\tA\t106.39.177.32", "ns2.jd.com.\t86400\tIN\tA\t111.206.226.10", "ns1.jd.com.\t86400\tIN\tA\t111.13.28.10", "msg jd.com. IN NS 32896 1 120 3 1 0 5 6 ", "jd.com. IN NS 0", "ns1.jd.com. IN A 0", "ns2.jd.com. IN A 0", "ns3.jd.com. IN A 0", "ns4.jd.com. IN A 0", "ns5.jd.com. IN A 0", "msg jd.com. IN A 32896 1 60 3 1 0 0 6 ", "jd.com. IN A 0" ]} ____________< PLAY RECAP > ------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||Client: ok=5 changed=3 failed=0 skipped=0 ignored=0Server: ok=8 changed=6 failed=0 skipped=0 ignored=0- ==ansible搭建邮箱服务==
- 另一个实验
vars变量
服务端
server_1: unboundserver_2: postfixserver_3: dovecotserver_ip: 10.0.0.101net: 10.0.0.0/24user: jiuuid: 1010passwd: passwd客户端
server_1: thunderbirdserver_ip: 10.0.0.101templates配置
服务端
unbound服务
- 位置: /usr/share/unbound/fedora-defaults.conf
- ==默认配置文件==
- 被包含在
/etc/unbound/unbound.conf主配置文件中!
server: ...... interface: "10.0.0.101" access-control: 10.0.0.0/24 allow .......# 只需要添加这两行即可!-
位置: /etc/unbound/local.d/example-com.conf
-
==子配置文件==—>自定义
-
检测:
unbound-checkconf- 关键字:
no errors
- 关键字:
-
重载:
unbound-control reload
# 定义本地域local-zone: "example.com." static
# A 记录:将域名指向服务器IPlocal-data: "dnsserver.example.com. IN A 10.0.0.101"local-data: "mailserver.example.com. IN A 10.0.0.101"local-data: "www.example.com. IN A 10.0.0.101"
# MX 记录:指定邮件服务器local-data: "example.com. IN MX 10 mailserver.example.com."Postfix服务
-
位置: /etc/postfix/main.cf
- ==主配置文件==
-
作用: 发信
# 配置文件兼容性级别(Postfix 3.8 语法)compatibility_level = 3.8
# 邮件队列存储目录queue_directory = /var/spool/postfix
# Postfix 可执行命令(如 postqueue, postsuper)所在目录command_directory = /usr/sbin
# Postfix 守护进程(如 smtpd, cleanup)所在目录daemon_directory = /usr/libexec/postfix
# Postfix 运行时数据(如缓存、会话)存储目录data_directory = /var/lib/postfix
# 运行 Postfix 进程的系统用户mail_owner = postfix
# 本邮件服务器的完全限定域名(FQDN)myhostname = mailserver.example.com
# 默认邮件域名(发件人地址中 @ 后面的部分)mydomain = example.com
# 发件人地址的默认域名(当发件人未指定域名时自动添加)myorigin = $mydomain
# 监听的网络接口(all 表示所有 IPv4 和 IPv6 接口)inet_interfaces = all
# 使用的 IP 协议版本(仅 IPv4,可改为 ipv6 或 all)inet_protocols = ipv4
# 本地投递的域名列表(这些域名的邮件直接投递到本地邮箱)mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
# 当收件人为本地不存在的用户时,返回的 SMTP 错误代码unknown_local_recipient_reject_code = 550
# 信任的客户端网络(来自这些网络的连接可以中继邮件,免 SASL 认证)mynetworks = {{net}}, 127.0.0.0/8
# 邮件别名映射表(用于将虚拟名称映射到真实用户)alias_maps = lmdb:/etc/aliases
# 邮件别名数据库(与 alias_maps 保持一致)alias_database = lmdb:/etc/aliases
# 用户家目录下的邮件存储格式(Maildir 格式)home_mailbox = Maildir/
# 外部邮箱投递命令(留空表示不使用外部命令投递)mailbox_command =
# 邮箱投递传输方式(通过 Dovecot LMTP 进行本地投递)mailbox_transport = lmtp:unix:private/dovecot-lmtp
# 调试信息详细级别(数字越大日志越详细)debug_peer_level = 2
# 调试器启动命令(当进程崩溃时调用的调试工具)debugger_command = PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin ddd $daemon_directory/$process_name $process_id & sleep 5
# sendmail 兼容命令的路径sendmail_path = /usr/sbin/sendmail.postfix
# newaliases 命令的路径(用于重建别名数据库)newaliases_path = /usr/bin/newaliases.postfix
# mailq 命令的路径(用于查看邮件队列)mailq_path = /usr/bin/mailq.postfix
# 用于执行特权操作的辅助组(如 postdrop)setgid_group = postdrop
# HTML 文档目录(no 表示不安装 HTML 文档)html_directory = no
# 手册页目录manpage_directory = /usr/share/man
# 示例配置目录sample_directory = /usr/share/doc/postfix/samples
# README 文档目录readme_directory = /usr/share/doc/postfix/README_FILES
# 是否在接收邮件时启用 TLS 加密(此处为禁用)smtpd_use_tls = no
# TLS 证书文件路径smtpd_tls_cert_file = /etc/pki/tls/certs/postfix.pem
# TLS 私钥文件路径smtpd_tls_key_file = /etc/pki/tls/private/postfix.key
# TLS 安全级别(may 表示尝试加密但不强制)smtpd_tls_security_level = may
# 发送邮件时用于验证对方证书的 CA 证书目录smtp_tls_CApath = /etc/pki/tls/certs
# 发送邮件时用于验证对方证书的 CA 证书文件smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt
# 发送邮件时的 TLS 安全级别(may 表示尝试加密但不强制)smtp_tls_security_level = may
# 默认的数据库类型(lmdb 为轻量级内存映射数据库)default_database_type = lmdb
# Postfix 动态链接库目录shlib_directory = /usr/lib64/postfix
# 元配置文件目录(通常包含动态模块配置)meta_directory = /etc/postfix
# SASL 认证后端类型(由 Dovecot 提供)smtpd_sasl_type = dovecot
# Dovecot 认证套接字路径(相对于 Postfix 队列目录)smtpd_sasl_path = private/auth
# 启用 SASL 认证smtpd_sasl_auth_enable = yes
# SASL 安全选项(禁止匿名登录)smtpd_sasl_security_options = noanonymous
# SASL 认证时使用的本地域名smtpd_sasl_local_domain = $myhostname
# 收件人限制规则(允许信任网络和已认证用户,拒绝未授权目标)smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destinationDovecot服务
- 位置: /etc/dovecot/dovecot.conf
- ==主配置文件==
# 启用的服务协议(IMAP 收信、POP3 收信、LMTP 本地投递)protocols = imap pop3 lmtp
# 监听的网络地址(* 表示所有 IPv4 地址,:: 表示所有 IPv6 地址)listen = *, ::
# 字典服务配置块(用于配额、反垃圾等扩展功能的键值存储)dict {}
# 加载 conf.d 目录下的所有 .conf 配置文件!include conf.d/*.conf
# 尝试加载 local.conf(如果文件存在则覆盖默认配置,通常用于本地自定义)!include_try local.conf- 位置: /etc/dovecot/conf.d/10-mail.conf
- ==邮件存储配置==
# ===== Dovecot 邮件存储与命名空间配置 =====
# 邮件存储位置和格式(使用 Maildir 格式,存储于用户家目录下的 Maildir 目录)mail_location = maildir:~/Maildir
# 定义默认收件箱命名空间(IMAP 客户端看到的文件夹结构)namespace inbox { # 标记此命名空间为主收件箱 inbox = yes
# 定义“已删除”邮件箱 mailbox Trash { # 客户端自动订阅此邮箱 auto = subscribe # 符合 IMAP 标准的特殊用途属性(\Trash 表示垃圾箱) special_use = \Trash }
# 定义“已发送”邮件箱 mailbox Sent { # 客户端自动订阅此邮箱 auto = subscribe # \Sent 属性表示已发送邮件 special_use = \Sent }
# 定义“草稿”邮件箱 mailbox Drafts { # 客户端自动订阅此邮箱 auto = subscribe # \Drafts 属性表示草稿 special_use = \Drafts }
# 定义“垃圾邮件”邮件箱 mailbox Junk { # 客户端自动订阅此邮箱 auto = subscribe # \Junk 属性表示垃圾邮件 special_use = \Junk }}
# 具有邮件操作特权的系统组(通常为 mail 组)mail_privileged_group = mail
# 允许访问邮件的有效用户 UID 最小值(低于此 UID 的用户将被拒绝访问,如 root)first_valid_uid = 1000
# 针对 indexer-worker 协议的特定配置(此处为空,表示不覆盖默认设置)protocol !indexer-worker {}
# mbox 格式文件写入时的锁定方式(fcntl 为文件锁,兼容性好)mbox_write_locks = fcntl- 位置: /etc/dovecot/conf.d/10-auth.conf
- ==认证配置==
disable_plaintext_auth = noauth_mechanisms = plain login!include auth-system.conf.ext- 位置: /etc/dovecot/conf.d/auth-system.conf.ext
- ==认证细节==
passdb { driver = pam}
userdb { driver = passwd}- 位置: /etc/dovecot/conf.d/10-ssl.conf
- ==SSL配置==
ssl = nossl_min_protocol = TLSv1.2- 位置: /etc/dovecot/conf.d/10-master.conf
- ==套接字配置==
# ===== Dovecot 主服务与监听配置 =====
# IMAP 登录服务(负责接受客户端 IMAP 连接)service imap-login { # IMAP 普通端口监听器(143 端口,无加密) inet_listener imap { } # IMAPS 加密端口监听器(993 端口,SSL/TLS 加密) inet_listener imaps { }}
# POP3 登录服务(负责接受客户端 POP3 连接)service pop3-login { # POP3 普通端口监听器(110 端口,无加密) inet_listener pop3 { } # POP3S 加密端口监听器(995 端口,SSL/TLS 加密) inet_listener pop3s { }}
# 邮件提交登录服务(用于客户端通过 SMTP 提交邮件,587 和 465 端口)service submission-login { # SMTP 提交端口(587 端口,通常配合 STARTTLS) inet_listener submission { } # SMTPS 加密提交端口(465 端口,SSL/TLS 直接加密) inet_listener submissions { }}
# LMTP 本地邮件投递服务(Postfix 通过此服务将邮件投递到用户邮箱)service lmtp { # Unix 套接字,供 Postfix 连接进行本地投递 unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0600 # 套接字权限(仅 owner 可读写) user = postfix # 套接字所有者 group = postfix # 套接字所属组 }}
# IMAP 主服务(登录后的 IMAP 会话处理)service imap {}
# POP3 主服务(登录后的 POP3 会话处理)service pop3 {}
# 邮件提交主服务(处理客户端提交邮件的后台逻辑)service submission {}
# 认证服务(验证用户密码,提供用户信息)service auth { # Unix 套接字,用于 Dovecot 内部用户数据库查询 unix_listener auth-userdb { mode = 0666 # 允许所有用户读写(内部通信使用) } # Unix 套接字,供 Postfix 进行 SASL 认证 unix_listener /var/spool/postfix/private/auth { mode = 0666 # 允许 Postfix 读写 }}
# 认证辅助工作进程(执行密码哈希等耗时操作)service auth-worker {}
# 字典服务(用于存储配额、反垃圾等扩展数据)service dict { # Unix 套接字,供其他 Dovecot 进程访问字典数据 unix_listener dict { }}[root@Server ~]# ss -lnutp | egrep "[:](53|25|143)"10.0.0.101:53 0.0.0.0:* users:(("unbound"10.0.0.101:53 0.0.0.0:* users:(("unbound"10.0.0.101:53 0.0.0.0:* users:(("unbound"10.0.0.101:53 0.0.0.0:* users:(("unbound" 0.0.0.0:143 0.0.0.0:* users:(("dovecot" 0.0.0.0:25 0.0.0.0:* users:(("master",10.0.0.101:53 0.0.0.0:* users:(("unbound"10.0.0.101:53 0.0.0.0:* users:(("unbound"10.0.0.101:53 0.0.0.0:* users:(("unbound"10.0.0.101:53 0.0.0.0:* users:(("unbound" [::]:143 [::]:* users:(("dovecot" [::]:25 [::]:* users:(("master",task任务
服务端
- name: "下载安装 {{server_1}} server" yum: name: "{{server_1}}" state: present
- name: "{{server_1}} 默认配置文件" blockinfile: path: /usr/share/unbound/fedora-defaults.conf block: | interface: {{server_ip}} access-control: {{net}} allow marker: "# {mark} Ansible" insertafter: "^.*interface-automatic"
- name: "{{server_1}} 子配置文件" template: src: example-com.conf.j2 dest: /etc/unbound/local.d/example-com.conf
- name: "检测配置是否有误" shell: "unbound-checkconf" register: unbound_check ignore_errors: yes
- name: "{{server_1}}启动&&开机自启动" systemd: name: "{{server_1}}" state: started enabled: yes
- name: "测试{{server_1}}" shell: "ss -lntup | grep unbound" register: check_re
- name: "输出测试结果" debug: msg: '恭喜! "{{server_1}}"服务配置检测成功,启动准备就绪!' when: check_re.stdout_lines is search "10.0.0.101:53"
- name: "修改本地DNS" copy: content: 'nameserver {{server_ip}}' dest: /etc/resolv.conf
- name: "nslookup 解析测试" shell: 'nslookup mailserver.example.com' register: nslookup_end
- name: "解析结果" debug: msg: "{{nslookup_end.stdout_lines}}"
- name: "下载 {{server_2}} && {{server_3}}" yum: name: "{{item}}" state: present loop: - "{{server_2}}" - "{{server_3}}"
- name: "{{server_2}} 主配置文件" template: src: main.cf.j2 dest: /etc/postfix/main.cf
- name: "{{server_2}}启动&&开机自启动" systemd: name: "{{server_2}}" state: started enabled: yes
- name: "批量配置{{server_3}}" template: src: "{{item.s}}" dest: "{{item.d}}" loop: - s: dovecot.conf.j2 d: /etc/dovecot/dovecot.conf - s: 10-mail.conf.j2 d: /etc/dovecot/conf.d/10-mail.conf - s: 10-auth.conf.j2 d: /etc/dovecot/conf.d/10-auth.conf - s: auth-system.conf.ext.j2 d: /etc/dovecot/conf.d/auth-system.conf.ext - s: 10-ssl.conf.j2 d: /etc/dovecot/conf.d/10-ssl.conf - s: 10-master.conf.j2 d: /etc/dovecot/conf.d/10-master.conf
- name: "{{server_3}}启动&&开机自启动" systemd: name: "{{server_3}}" state: started enabled: yes
- name: "所有服务端口测试" shell: "ss -lnutp | egrep '[:](53|25|143)'" register: result
- name: "打印输出最后结果" debug: msg: "{{result.stdout_lines}}"
- name: Create group {{user}} group: name: "{{user}}" gid: "{{uid}}"
- name: Create user {{user}} user: name: "{{user}}" group: "{{user}}" uid: "{{uid}}" shell: /bin/bash create_home: true
- name: Echo passwd {{user}} shell: 'echo "{{passwd}}" | passwd --stdin "{{user}}"'
- name: Test {{user}} shell: "tail -1 /etc/passwd" register: user_re
- name: Print user_re debug: var: user_re.stdout_lines客户端
- name: Configuer dns file copy: content: 'nameserver {{server_ip}}' dest: /etc/resolv.conf
- name: dig test command: 'dig -t MX example.com' register: dig_re
- name: Print dig_re debug: msg: "{{dig_re.stdout_lines}}"
- name: Install {{server_1}} server yum: name: "{{server_1}}" state: presenthandler触发器
服务端
- name: Restart "{{server_1}}" systemd: name: "{{server_1}}" state: restarted when: unbound_check.stdout_lines is search "no errors"主任务
- name: 配置 Server 主机 hosts: Server roles: - Server
- name: 配置 Client 主机 hosts: Client roles: - ClientStart
1)目录结构[root@Zabbix roles]# tree /ansible/roles//ansible/roles/├── Client│ ├── files│ ├── handlers│ ├── tasks│ │ └── main.yml│ ├── templates│ └── vars│ └── main.yml├── Server│ ├── files│ ├── handlers│ │ └── main.yml│ ├── tasks│ │ └── main.yml│ ├── templates│ │ ├── 10-auth.conf.j2│ │ ├── 10-mail.conf.j2│ │ ├── 10-master.conf.j2│ │ ├── 10-ssl.conf.j2│ │ ├── auth-system.conf.ext.j2│ │ ├── dovecot.conf.j2│ │ ├── example-com.conf.j2│ │ └── main.cf.j2│ └── vars│ └── main.yml└── site.yml
2)语法检测[root@Zabbix roles]# ansible-playbook --syntax-check site.ymlplaybook: site.yml
3)运行剧本'✅️全部恢复快照--->重头开始✅️'[root@Zabbix roles]# ansible-playbook site.yml _____________________< PLAY [配置 Server 主机] > --------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || _____________________________________< TASK [Server : 下载安装 unbound server] > ------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] ________________________________< TASK [Server : unbound 默认配置文件] > -------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] _______________________________< TASK [Server : unbound 子配置文件] > ------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
changed: [Server] __________________________< TASK [Server : 检测配置是否有误] > -------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] __________________________________< TASK [Server : unbound启动&&开机自启动] > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] ___________________________< TASK [Server : 测试unbound] > --------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] ________________________< TASK [Server : 输出测试结果] > ------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
ok: [Server] => { "msg": "恭喜! \"unbound\"服务配置检测成功,启动准备就绪!"} _________________________< TASK [Server : 修改本地DNS] > ------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] _______________________________< TASK [Server : nslookup 解析测试] > ------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] ______________________< TASK [Server : 解析结果] > ---------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ok: [Server] => { "msg": [ "Server:\t\t10.0.0.101", "Address:\t10.0.0.101#53", "", "Name:\tmailserver.example.com", "Address: 10.0.0.101" ]} _______________________________________< TASK [Server : 下载 postfix && dovecot] > --------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] => (item=postfix)changed: [Server] => (item=dovecot) _______________________________< TASK [Server : postfix 主配置文件] > ------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] __________________________________< TASK [Server : postfix启动&&开机自启动] > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] _____________________________< TASK [Server : 批量配置dovecot] > ----------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] => (item={'s': 'dovecot.conf.j2', 'd': '/etc/dovecot/dovecot.conf'})changed: [Server] => (item={'s': '10-mail.conf.j2', 'd': '/etc/dovecot/conf.d/10-mail.conf'})changed: [Server] => (item={'s': '10-auth.conf.j2', 'd': '/etc/dovecot/conf.d/10-auth.conf'})changed: [Server] => (item={'s': 'auth-system.conf.ext.j2', 'd': '/etc/dovecot/conf.d/auth-system.conf.ext'})changed: [Server] => (item={'s': '10-ssl.conf.j2', 'd': '/etc/dovecot/conf.d/10-ssl.conf'})changed: [Server] => (item={'s': '10-master.conf.j2', 'd': '/etc/dovecot/conf.d/10-master.conf'}) __________________________________< TASK [Server : dovecot启动&&开机自启动] > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] __________________________< TASK [Server : 所有服务端口测试] > -------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] __________________________< TASK [Server : 打印输出最后结果] > -------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ok: [Server] => { "msg": [ "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=9)) ", "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=7)) ", "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=5)) ", "udp UNCONN 0 0 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=3)) ", "tcp LISTEN 0 100 0.0.0.0:25 0.0.0.0:* users:((\"master\",pid=5473,fd=13)) ", "tcp LISTEN 0 100 0.0.0.0:143 0.0.0.0:* users:((\"dovecot\",pid=7419,fd=39))", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=10))", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=8)) ", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=6)) ", "tcp LISTEN 0 256 10.0.0.101:53 0.0.0.0:* users:((\"unbound\",pid=3211,fd=4)) ", "tcp LISTEN 0 100 [::]:143 [::]:* users:((\"dovecot\",pid=7419,fd=40))" ]} __________________________________< TASK [Server : Create group jiu] > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] _________________________________< TASK [Server : Create user jiu] > --------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] _________________________________< TASK [Server : Echo passwd jiu] > --------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] __________________________< TASK [Server : Test jiu] > -------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Server] _______________________________< TASK [Server : Print user_re] > ------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ok: [Server] => { "user_re.stdout_lines": [ "jiu❌1010:1010::/home/jiu:/bin/bash" ]} _____________________< PLAY [配置 Client 主机] > --------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ____________________________________< TASK [Client : Configuer dns file] > ------------------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Client] __________________________< TASK [Client : dig test] > -------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||changed: [Client] ______________________________< TASK [Client : Print dig_re] > ------------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ok: [Client] => { "msg": [ "", "; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.16 <<>> -t MX example.com", ";; global options: +cmd", ";; Got answer:", ";; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36826", ";; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1", "", ";; OPT PSEUDOSECTION:", "; EDNS: version: 0, flags:; udp: 1232", ";; QUESTION SECTION:", ";example.com.\t\t\tIN\tMX", "", ";; ANSWER SECTION:", "example.com.\t\t3600\tIN\tMX\t10 mailserver.example.com.", "", ";; Query time: 0 msec", ";; SERVER: 10.0.0.101#53(10.0.0.101)", ";; WHEN: 六 4月 11 18:26:01 CST 2026", ";; MSG SIZE rcvd: 67" ]} ____________________________________________< TASK [Client : Install thunderbird server] > -------------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||ok: [Client] ____________< PLAY RECAP > ------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||Client: ok=4 changed=2 failed=0Server: ok=22 changed=18 failed=0截图验证






实验扩展
| 特性 | 之前实验 | 老师的优化版本 |
|---|---|---|
| 邮件域数量 | 仅 example.com | example.com + test.com |
| DNS 类型 | local-zone (非权威) | auth-zone (权威区域) |
| Postfix 域处理 | mydestination 直接包含 example.com | 使用 virtual_alias_domains 统一管理多域 |
| 通信安全 | 全程明文 (25, 143) | 全程加密 (465, 993) + 强加密策略 |
| 证书 | 无或未启用 | 自签名证书 (带SAN) |
| 客户端配置 | 明文端口 | 加密端口 + 证书例外 |
扩展DNS
[root@Server ~]# vim /etc/unbound/conf.d/test-com.confauth-zone: name: "test.com" for-downstream: yes for-upstream: yes zonefile: "/etc/unbound/local.d/test-com.zone"[root@Server ~]# vim /etc/unbound/local.d/test-com.zone$TTL 3600@ IN SOA dnsserver.test.com. admin.test.com. ( 2026041301 3600 300 86400 600 )@ IN NS dnsserver.test.com.@ IN MX 10 mailserver.test.com.mailserver IN A 10.0.0.101dnsserver IN A 10.0.0.101[root@Server ~]# unbound-checkconfunbound-checkconf: no errors in /etc/unbound/unbound.conf[root@Server ~]# unbound-control reloadok[root@Server ~]# systemctl restart unbound# 重启服务配置 Postfix 支持多域
- 目的:让 Postfix 能接收并投递
@example.com和@test.com的邮件
1)修改配置文件[root@Server ~]# vim /etc/postfix/main.cf'有替换,有添加'# 注释掉原有的 mydomain# mydomain = example.com
# 在文件末尾添加virtual_alias_domains = example.com, test.comvirtual_alias_maps = lmdb:/etc/postfix/virtual
2)创建虚拟用户映射文件[root@Server ~]# vim /etc/postfix/virtual# 清空里面的内容jiu@example.com jiuheima@test.com heima
3)创建系统用户并生成映射数据库[root@Server ~]# useradd heima[root@Server ~]# echo oldboy123.com | passwd --stdin heima[root@Server ~]# postmap lmdb:/etc/postfix/virtual[root@Server ~]# systemctl restart postfix配置 TLS/SSL 加密
目的:保护邮件传输过程中的用户名、密码和邮件内容
[root@Server ~]# vim san.cnfprompt = nodefault_md = sha256req_extensions = v3_reqdistinguished_name = req_distinguished_name
[ req_distinguished_name ]C = CNST = BeijingL = BeijingO = MailServerCN = mailserver.example.com
[ v3_req ]keyUsage = keyEncipherment, dataEnciphermentextendedKeyUsage = serverAuth, clientAuthsubjectAltName = @alt_names
[ alt_names ]DNS.1 = mailserver.example.comDNS.2 = mailserver.test.com
req: Can't open "/etc/ssl/private/mail.key" for writing, No such file or directory'
[root@Server ~]# mkdir /etc/ssl/private/
# 执行 openssl 命令生成证书和密钥openssl req -new -x509 -days 365 -nodes \ -newkey rsa:2048 \ -keyout /etc/ssl/private/mail.key \ -out /etc/ssl/certs/mail.crt \ -config san.cnf -extensions v3_req.+......+.....+...............+...+......+.+.........+...+......+..+++++++++++++++++++++++++++++++++++++++*.+...+.+..+.......+.....+.+......+...+..+.+..+.............+.........+........+...+.........+.+.....+...+......+.+.....+....+.........+++++++++++++++++++++++++++++++++++++++*........+...+....+...+..+.+......+...+.....+.....................+......+.+...+..........................+......+.............+...........+....+...........+...+
[root@Server ~]# vim /etc/postfix/main.cf'有修改,有添加'# 在接收邮件时启用 TLS 加密smtpd_use_tls = yes# 由no--->yes
# TLS 证书文件路径smtpd_tls_cert_file = /etc/ssl/certs/mail.crt# TLS 私钥文件路径smtpd_tls_key_file = /etc/ssl/private/mail.key# 这两个证书位置也要改变!
# TLS 安全级别(may 表示尝试加密但不强制)smtpd_tls_security_level = may
# 新增安全强化smtpd_tls_ciphers = highsmtp_tls_ciphers = highsmtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
[root@Server ~]# vim /etc/postfix/master.cf# ⚠️注意后面还有个s-->smtps⚠️smtps inet n - n - - smtpd -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
[root@Server ~]# vim /etc/dovecot/conf.d/10-ssl.confssl = yesssl_min_protocol = TLSv1.2ssl_cert = </etc/ssl/certs/mail.crtssl_key = </etc/ssl/private/mail.key[root@Server ~]# vim /etc/dovecot/conf.d/10-auth.conf'添加以下配置'auth_username_format = %n# 去掉邮箱域名,只保留本地用户名(如 jiu@example.com → jiu)[root@Server ~]# cat /etc/dovecot/conf.d/10-auth.conf | grep -v "^#" | grep -v "^$"disable_plaintext_auth = noauth_mechanisms = plain loginauth_username_format = %n!include auth-system.conf.ext[root@Server ~]# ls -l /etc/dovecot/conf.d/auth-system.conf.ext-rw-r--r-- 1 root root 56 Apr 13 16:26 /etc/dovecot/conf.d/auth-system.conf.ext[root@Server ~]# chmod 600 /etc/dovecot/conf.d/auth-system.conf.ext[root@Server ~]# systemctl restart postfix dovecot命令行测试与验证
'服务端测试!'[root@Server ~]# doveadm auth test jiu passwdpassdb: jiu auth ❌️failed❌️extra fields: user=jiu# 用户认证失败❌️[root@Server ~]# doveadm auth test jiu passwdpassdb: jiu auth succeededextra fields: user=jiu✅ 这说明 Dovecot 的认证已经完全成功!→ jiu 的密码确实是 oldboy123.com,且 PAM 认证工作正常!==================================1. Thunderbird 中用户名填写错误❌ 错误:只填 jiu✅ 正确:必须填 完整邮箱地址 → jiu@example.com💡 虽然 Dovecot 用系统用户 jiu,但 Postfix 的虚拟域配置要求客户端使用 完整邮箱 登录(因为你在 /etc/postfix/virtual 中定义的是 jiu@example.com)=================================='客户端测试'[root@test ~]# openssl s_client -connect mailserver.example.com:465 -quietdepth=0 C = CN, ST = Beijing, L = Beijing, O = MailServer, CN = mailserver.example.comverify error:num=20:unable to get local issuer certificateverify return:1depth=0 C = CN, ST = Beijing, L = Beijing, O = MailServer, CN = mailserver.example.comverify error:num=21:unable to verify the first certificateverify return:1220 mailserver.example.com ESMTP Postfix虽然有证书警告(verify error),但这是自签名证书的正常现象。最关键的是:收到了 220 ... ESMTP Postfix 响应 → 说明 SMTPS 服务完全可用'下面是完整的显示证书!'[root@test ~]# openssl s_client -connect mailserver.example.com:465---SSL handshake has read 1669 bytes and written 415 bytes---New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384Server public key is 2048 bitSecure Renegotiation IS supportedCompression: NONEExpansion: NONENo ALPN negotiatedSSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES256-GCM-SHA384 Session-ID: EA38BC7F3521EDEE77DB1F7E0F82A8D55A6AE88574FFE8BA83F938DC7511D5D1 Session-ID-ctx: Master-Key: 9DB77C325F246D8774B59C680313AE5940CA02C8AD43A024C1989D30C0A990E532776C7747DEA213F7C209B7E8D503A5 Key-Arg : None Krb5 Principal: None PSK identity: None PSK identity hint: None TLS session ticket lifetime hint: 7200 (seconds) Start Time: 1776079858 Timeout : 300 (sec) Verify return code: 21 (unable to verify the first certificate)---220 mailserver.example.com ESMTP Postfix
[root@test ~]# openssl s_client -connect mailserver.example.com:993 -quiet......* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN AUTH=LOGIN] Dovecot ready.# 说明 IMAP 服务正常,且证书可接受(即使有警告)a LOGIN jiu@example.com passwd# 测试登录a NO [AUTHENTICATIONFAILED] Authentication failed.❌️📌 关键真相:Dovecot 默认 不会自动剥离域名!当客户端发送 LOGIN jiu@example.com passwd,Dovecot 会尝试用 PAM 认证用户名 jiu@example.com —— 而系统中根本没有这个用户!所以失败✅ 正确解决方案:让 Dovecot 自动提取本地用户名(去掉 @example.com)vim /etc/dovecot/conf.d/10-auth.conf# 去掉邮箱域名,只保留本地用户名(如 jiu@example.com → jiu)auth_username_format = %n[root@Server ~]# systemctl restart dovecot[root@test ~]# openssl s_client -connect mailserver.example.com:993 -quietdepth=0 C = CN, ST = Beijing, L = Beijing, O = MailServer, CN = mailserver.example.comverify error:num=20:unable to get local issuer certificateverify return:1depth=0 C = CN, ST = Beijing, L = Beijing, O = MailServer, CN = mailserver.example.comverify error:num=21:unable to verify the first certificateverify return:1# 并再次测试* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN AUTH=LOGIN] Dovecot ready.a LOGIN jiu@example.com passwda OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY PREVIEW STATUS=SIZE SAVEDATE LITERAL+ NOTIFY SPECIAL-USE] Logged in截图验证








文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
相关文章智能推荐
1
Ceph集群开篇
存储技术Ceph分布式存储集群入门,详解RADOS架构、MON/MGR/OSD核心组件及RBD/CephFS/RadosGW三种存储接口的部署与管理
2
函数与数组
Shell脚本Shell函数定义与数组操作,涵盖局部/全局变量、递归函数及数组遍历
3
循环与case多分支
Shell脚本Shell循环结构与case多分支语句,涵盖for/while/until循环及实战脚本
4
数值运算与if条件判断
Shell脚本Shell数值运算方法与if条件判断结构,涵盖整数/字符串比较及文件测试
5
Shell编程基础
Shell脚本Shell脚本基础入门,涵盖变量、引号规则、条件测试及脚本调试方法
随机文章随机推荐



