date: 2020-04-03title: ansible初始化服务器 #标题
tags: 初始化服务器 #标签
categories: ansible # 分类
用于记录下初始化服务器的playbook。篇幅较长,需要耐心。
入口文件
我是将playbook文件封装在了shell脚本中,将此shell脚本作为入口文件
[root@ansible ansible-init-system]# cat ansible-init.sh#!/usr/bin/env bash# __author__ == "Ray"# __Email__ == "lv916551516@163.com"Usage(){echo -e "$0 脚本使用说明-f: 指定要执行的playbook(.yml)文件-h: 指定远程主机,多个主机以冒号分割-u: 指定远程主机的用户,(支持sudo),默认值为root-p: 指定远程主机端口号"}cur_dir=$(cd $(dirname ${BASH_SOURCE[0]}); pwd) #获取当前目录if [[ $# -eq 0 ]];thenUsageexit 1elif [[ $1 == "--help" ]];thenUsageexit 2fi#1.指定参数赋值while getopts ":f:h:u:p:" values;docase "$values" in"f")file="$OPTARG";;"h")host="$OPTARG";;"u")user="$OPTARG";;"p")port="$OPTARG";;"?")echo "无此选项 $OPTARG";;":")echo "此选项无值 $OPTARG";;*)Usage;;esacdone# 2.确认参数if [[ -n ${file} ]];thenecho -e "playbook文件:${file}远端主机: ${host}远端主机用户:${user}远端主机端口: ${port}"read -p "回车确认执行......" OK# 3.执行playbookansible-playbook ${cur_dir}/${file} --extra-vars "remote_host=$host ansible_ssh_user=$user ansible_ssh_port=$port"fi
playbook入口文件
[root@ansible ansible-init-system]# cat szy_init_system.yml---- hosts: '{{ remote_host }}'become: yesbecome_method: sudogather_facts: noroles: # 下面定义了各种角色,如果哪些角色无需执行,注释掉即可- vim- epel_config- firewalld- selinux- set-hostname# - certbot- python3- zabbix-agent- rsync- setup-system- yum- change-root-password # 执行此role前,务必保证被控端已创建拥有sudo权限的普通用户
roles文件
好,接下来我们来看看roles文件写的都是什么。
[root@ansible ansible-init-system]# ls roles/ # 以下是所有role的目录certbot firewalld selinux vimchange-root-password python3 set-hostname yumepel_config rsync setup-system zabbix-agent
vim优化——vim
[root@ansible roles]# tree -L 2 vim # vim角色的目录结构如下vim├── files│ ├── vimfiles│ └── vimrc├── meta│ └── main.yml├── Readme.txt└── tasks└── main.yml# 部分重要文件内容[root@ansible roles]# cat vim/tasks/main.yml # tasks/main.yml文件---- name: copy vimfilescopy: src=vimfiles/ dest=/usr/share/vim/vimfiles/- name: copy vimrccopy: src=vimrc dest=/etc/vimrc mode=0644[root@ansible roles]# cat vim/files/vimrcif v:progname =~? "evim"finishendifset nocompatibleset backspace=indent,eol,startif has("vms")set nobackupelseset backupendifset history=50set rulerset showcmdset incsearchmap Q gqinoremap <C-U> <C-G>u<C-U>"if has('mouse')"" set mouse=a"endifif &t_Co > 2 || has("gui_running")syntax onset hlsearchendifif has("autocmd")filetype plugin indent onaugroup vimrcExau!autocmd FileType text setlocal textwidth=78autocmd BufReadPost *\ if line("'\"") > 1 && line("'\"") <= line("$") |\ exe "normal! g`\"" |\ endifaugroup ENDelseset autoindentendif " has("autocmd")if !exists(":DiffOrig")command DiffOrig vert new | set bt=nofile | r ++edit # | 0d_ | diffthis\ | wincmd p | diffthisendif" ---------------------------------------------------------------------------"colorsset t_Co=256syntax enablesyntax oncolorscheme molokai" 不备份文件set nobackup" 自动格式化set formatoptions=tcrqn" 继承前一行的缩进方式,特别适用于多行注释set autoindent" 为C程序提供自动缩进set smartindent" 使用C样式的缩进set cindent" 设置以缩进的方式自动折叠和设置快捷方式" set foldmethod=indent" 统一缩进为4set softtabstop=4set shiftwidth=4" 在行和段开始处使用制表符set smarttab" 制表符为4set tabstop=4" 用空格代替制表符set expandtab" 不要换行set nowrap" 粘贴模式set paste" 我的状态行显示的内容(包括文件类型和解码)set statusline=%F%m%r%h%w\[POS=%l,%v][%p%%]\%{strftime(\"%d/%m/%y\ -\ %H:%M\")}" 总是显示状态行set laststatus=2" 高亮显示匹配的括号set showmatchset fileencodings=utf-8,ucs-bom,gb18030,gbk,gb2312,cp936set termencoding=utf-8set encoding=utf-8[root@ansible roles]# cat vim/meta/main.yml # 由于安装的系统没有vim指令,所以必须依赖于yum角色先进行安装vim---dependencies:- { role: yum }
安装基本工具——yum
[root@ansible roles]# tree -C yum # yum角色的目录结构yum├── meta│ └── main.yml├── Readme.txt├── tasks│ └── main.yml└── vars└── main.yml[root@ansible roles]# cat yum/tasks/main.yml # 安装软件---- name: Install epel repoyum:name: 'epel-release.noarch'state: latest- name: install the 'Development tools' package groupyum:name: "@Development tools"state: present- name: Install packagesyum:name: "{{ pkg_list }}" # 这里是引用的变量state: latest[root@ansible roles]# cat yum/vars/main.yml # 下面就是定义的上面引用的变量# 需要安装什么,只需要在此文件下增加即可pkg_list:- net-tools- bash-completion- bash-completion-extras- chrony- cronie- supervisor- crontabs- dstat- python-pip- file- htop- iftop- rsync- iproute- jq- less- mlocate- net-tools- nload- nmap- nmap-ncat- procps- screen- sysstat- telnet- traceroute- tree- unzip- zlib-devel- vim- which- openssl- openssl-devel- bzip2-devel- ncurses-devel- sqlite-devel- readline-devel- tk-devel- gdbm-devel- db4-devel- libpcap-devel- xz-devel- libselinux-python- lrzsz[root@ansible roles]# cat yum/meta/main.yml # 嗯,yum角色又依赖epel_config角色---dependencies:- { role: epel_config }
配置epel-repo源——epel_config
[root@ansible roles]# tree epel_config/ # 目录结构如下epel_config/└── tasks└── main.yml[root@ansible roles]# cat epel_config/tasks/main.yml # 任务列表---# 备份原有repo文件- name: backup origin yum reposshell:cmd: "mkdir bak;mv *.repo bak"chdir: /etc/yum.repos.dcreates: /etc/yum.repos.d/bak# 添加repo源- name: add os repo and epel repoyum_repository:name: "{{item.name}}"description: "{{item.name}} repo"baseurl: "{{item.baseurl}}"file: "{{item.name}}"enabled: 1gpgcheck: 0reposdir: /etc/yum.repos.dloop:- name: osbaseurl: "https://mirrors.tuna.tsinghua.edu.cn/centos/7/os/$basearch"- name: epelbaseurl: "http://mirrors.aliyun.com/epel/7/$basearch"
防火墙配置——firewalld
[root@ansible roles]# tree firewalld/ # 目录结构firewalld/├── files│ └── work.xml # 这个文件是自定义的防火墙规则,就不看了├── handlers│ └── main.yml└── tasks└── main.yml[root@ansible roles]# cat firewalld/tasks/main.yml # 查看任务列表---# 安装firewalld- name: Install firewalld.yum:name: firewalldstate: latest#确保服务启动- name: Ensure firewalld is started and enabled at boot.systemd:name: firewalldstate: startedenabled: yes#设置firewalld的默认区域- name: Set work as default policycommand: firewall-cmd --set-default-zone=worknotify: reload firewalld# 初始 zone 配置- name: copy work.xml for firewalldcopy: src=work.xml dest=/etc/firewalld/zones/work.xml mode=0600notify: reload firewalld[root@ansible roles]# cat firewalld/handlers/main.yml # 触发操作---#重载firewalld- name: reload firewalldcommand: firewall-cmd --reload
selinux配置——selinux
[root@ansible roles]# tree selinux/ # 目录结构selinux/└── tasks└── main.yml[root@ansible roles]# cat selinux/tasks/main.yml # 任务列表---#改变selinux状态为disable- name: 将selinu修改为disabled,提示需要重启生效selinux:policy: targetedstate: disabled#临时禁用- name: 临时设置selinux关闭,如果关闭,忽略shell: setenforce 0ignore_errors: True
更改主机名——set-hostname
[root@ansible roles]# tree set-hostname/ # 目录结构set-hostname/├── tasks│ └── main.yml└── vars└── main.yml[root@ansible roles]# cat set-hostname/tasks/main.yml # 任务列表---#设置控制机的主机名- name: set hostnamehostname:name: "{{item.name}}"when: item.host == inventory_hostnameloop: "{{hostnames}}"[root@ansible roles]# cat set-hostname/vars/main.yml # 将IP与想要更改的主机名写在下面即可---hostnames:- host: 192.168.20.9name: node01- host: 192.168.20.10name: node02
安装python3——Python3
[root@ansible roles]# tree python3/ # 目录结构python3/├── files│ └── Python-3.7.1.tgz # 这个为python3的软件包├── Readme.txt├── tasks│ └── main.yml└── vars└── main.yml[root@ansible roles]# cat python3/tasks/main.yml # 任务列表---#安装依赖包- name: "安装python3的依赖包"yum:name: "{{ python3_depend }}"state: latest#拷贝Python-3.7.1- name: "copy Python3.7.1 to dest"copy: src=Python-3.7.1.tgz dest=/usr/local/src/Python-3.7.1.tgz#编译安装python3.7.1- name: "compile install"shell: cd /usr/local/src/;tar zxf Python-3.7.1.tgz; cd Python-3.7.1;./configure --prefix=/usr/local/python3.7;make;make install#删除之前存在的/usr/bin/python3- name: "删除已存在的python3"shell: rm -f /usr/bin/python3args:removes: /usr/bin/python3#软连接python3- name: "ln -s python3"file: src=/usr/local/python3.7/bin/python3 dest=/usr/bin/python3 state=link[root@ansible roles]# cat python3/vars/main.yml # 定义变量,以下是python3依赖的程序---python3_depend:- zlib-devel- bzip2-devel- openssl-devel- ncurses-devel- sqlite-devel- readline-devel- tk-devel- gdbm-devel- db4-devel- libpcap-devel- xz-devel- libffi-devel
同步工具——rsync
[root@ansible roles]# tree rsync/ # 目录结构rsync/├── files│ └── rsyncd.conf # 此为我这里定义的配置文件,不便展示,根据需要定义自己的配置文件即可├── handlers│ └── main.yml└── tasks└── main.yml[root@ansible roles]# cat rsync/tasks/main.yml # 任务列表---# 安装rsync服务- name: "install rsync"package:name: '{{ item }}'state: presentwith_items:- rsync- name: "copy rsyncd.conf to dest_host"copy: src=rsyncd.conf dest=/etc/rsyncd.confnotify: restart rsyncd[root@ansible roles]# cat rsync/handlers/main.yml # 触发任务---#restart rsyncd- name: restart rsyncdsystemd: name=rsyncd state=restarted
更改root密码——change-root-password
[root@ansible roles]# tree change-root-password/ # 目录结构change-root-password/└── tasks└── main.yml[root@ansible roles]# cat change-root-password/tasks/main.yml # 任务列表---- name: 设置主机root密码shell: pwd=`openssl rand -base64 20` && echo $pwd >/root/.rootpwd && echo "root:$pwd" | chpasswd && echo $pwdregister: password# 此任务存在些bug,万一服务器没有其他拥有sudo权限的用户,那么将无法得到修改后的root密码# 解决办法:增加发邮件功能,将root密码发送到邮箱。或者,在执行此role前创建拥有sudo权限的用户
安装zabbix-agent
[root@ansible roles]# tree zabbix-agent/ # 目录结构如下zabbix-agent/└── tasks└── main.yml[root@ansible roles]# cat zabbix-agent/tasks/main.yml # 任务列表- name: Install zabbix-agentshell: rpm -ivh http://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-agent-4.0.0-2.el7.x86_64.rpmargs:creates: /usr/sbin/zabbix_agentd # 如果指定的文件存在,则不执行此任务
系统优化——setup-system
[root@ansible roles]# tree setup-system/ # 目录结构setup-system/├── files│ ├── bashrc│ ├── ctn│ ├── limits.conf│ ├── profile│ ├── rc.local│ ├── rectn│ └── sysctl.conf├── handlers│ └── main.yml├── Readme.txt└── tasks└── main.yml# 接下来将依次展示files目录下的文件[root@ansible roles]# cat setup-system/files/bashrc # bashrc文件# /etc/bashrc# System wide functions and aliases# Environment stuff goes in /etc/profile# It's NOT a good idea to change this file unless you know what you# are doing. It's much better to create a custom.sh shell script in# /etc/profile.d/ to make custom changes to your environment, as this# will prevent the need for merging in future updates.# are we an interactive shell?if [ "$PS1" ]; thenif [ -z "$PROMPT_COMMAND" ]; thencase $TERM inxterm*|vte*)if [ -e /etc/sysconfig/bash-prompt-xterm ]; thenPROMPT_COMMAND=/etc/sysconfig/bash-prompt-xtermelif [ "${VTE_VERSION:-0}" -ge 3405 ]; thenPROMPT_COMMAND="__vte_prompt_command"elsePROMPT_COMMAND='printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"'fi;;screen*)if [ -e /etc/sysconfig/bash-prompt-screen ]; thenPROMPT_COMMAND=/etc/sysconfig/bash-prompt-screenelsePROMPT_COMMAND='printf "\033k%s@%s:%s\033\\" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"'fi;;*)[ -e /etc/sysconfig/bash-prompt-default ] && PROMPT_COMMAND=/etc/sysconfig/bash-prompt-default;;esacfi# Turn on parallel historyshopt -s histappendhistory -a# Turn on checkwinsizeshopt -s checkwinsize[ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ "# You might want to have e.g. tty in prompt (e.g. more virtual machines)# and console windows# If you want to do so, just add e.g.# if [ "$PS1" ]; then# PS1="[\u@\h:\l \W]\\$ "# fi# to your custom modification shell script in /etc/profile.d/ directoryfiif ! shopt -q login_shell ; then # We're not a login shell# Need to redefine pathmunge, it get's undefined at the end of /etc/profilepathmunge () {case ":${PATH}:" in*:"$1":*);;*)if [ "$2" = "after" ] ; thenPATH=$PATH:$1elsePATH=$1:$PATHfiesac}# By default, we want umask to get set. This sets it for non-login shell.# Current threshold for system reserved uid/gids is 200# You could check uidgid reservation validity in# /usr/share/doc/setup-*/uidgid fileif [ $UID -gt 199 ] && [ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]; thenumask 002elseumask 022fiSHELL=/bin/bash# Only display echos from profile.d scripts if we are no login shell# and interactive - otherwise just process them to set envvarsfor i in /etc/profile.d/*.sh; doif [ -r "$i" ]; thenif [ "$PS1" ]; then. "$i"else. "$i" >/dev/nullfifidoneunset iunset -f pathmungefi# vim:ts=4:sw=4HISTFILESIZE=100HISTSIZE=100HISTTIMEFORMAT='[%Y.%m.%d %H:%M:%S]'export HISTTIMEFORMAT[root@ansible roles]# cat setup-system/files/limits.conf # limits.conf文件# /etc/security/limits.conf##This file sets the resource limits for the users logged in via PAM.#It does not affect resource limits of the system services.##Also note that configuration files in /etc/security/limits.d directory,#which are read in alphabetical order, override the settings in this#file in case the domain is the same or more specific.#That means for example that setting a limit for wildcard domain here#can be overriden with a wildcard setting in a config file in the#subdirectory, but a user specific setting here can be overriden only#with a user specific setting in the subdirectory.##Each line describes a limit for a user in the form:##<domain> <type> <item> <value>##Where:#<domain> can be:# - a user name# - a group name, with @group syntax# - the wildcard *, for default entry# - the wildcard %, can be also used with %group syntax,# for maxlogin limit##<type> can have the two values:# - "soft" for enforcing the soft limits# - "hard" for enforcing hard limits##<item> can be one of the following:# - core - limits the core file size (KB)# - data - max data size (KB)# - fsize - maximum filesize (KB)# - memlock - max locked-in-memory address space (KB)# - nofile - max number of open file descriptors# - rss - max resident set size (KB)# - stack - max stack size (KB)# - cpu - max CPU time (MIN)# - nproc - max number of processes# - as - address space limit (KB)# - maxlogins - max number of logins for this user# - maxsyslogins - max number of logins on the system# - priority - the priority to run user process with# - locks - max number of file locks the user can hold# - sigpending - max number of pending signals# - msgqueue - max memory used by POSIX message queues (bytes)# - nice - max nice priority allowed to raise to values: [-20, 19]# - rtprio - max realtime priority##<domain> <type> <item> <value>##* soft core 0#* hard rss 10000#@student hard nproc 20#@faculty soft nproc 20#@faculty hard nproc 50#ftp hard nproc 0#@student - maxlogins 4* soft nofile 65535* hard nofile 65535* soft nproc 65535* hard nproc 65535# End of file[root@ansible roles]# cat setup-system/files/rc.local # rc.local文件#!/bin/bash# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES## It is highly advisable to create own systemd services or udev rules# to run scripts during boot instead of using this file.## In contrast to previous versions due to parallel execution during boot# this script will NOT be run after all other services.## Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure# that this script will be executed during boot.touch /var/lock/subsys/localulimit -SHn 65535if test -f /sys/kernel/mm/transparent_hugepage/enabled; thenecho never > /sys/kernel/mm/transparent_hugepage/enabled[root@ansible roles]# cat setup-system/files/sysctl.conf # 系统内核优化kernel.sysrq = 0kernel.core_uses_pid = 1fs.file-max=655360kernel.msgmnb = 65536kernel.msgmax = 65536kernel.shmmax = 68719476736kernel.shmall = 4294967296kernel.pid_max = 655360net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_tw_recycle = 0net.ipv4.tcp_max_tw_buckets = 262144net.ipv4.tcp_fin_timeout = 30net.ipv4.tcp_timestamps = 1net.ipv4.tcp_sack = 1net.ipv4.tcp_window_scaling = 1net.ipv4.tcp_ecn = 0net.ipv4.tcp_keepalive_time = 600net.ipv4.tcp_keepalive_intvl = 30net.ipv4.tcp_keepalive_probes = 3net.ipv4.tcp_max_orphans = 655360net.ipv4.tcp_max_syn_backlog = 262144net.ipv4.tcp_mem = 65536 131072 262144net.ipv4.udp_mem = 65536 131072 262144net.ipv4.tcp_rmem = 4096 87380 16777216net.ipv4.tcp_wmem = 4096 16384 16777216net.ipv4.ip_local_port_range = 1024 65535net.ipv4.route.gc_timeout = 100# 禁止icmp重定向报文net.ipv4.conf.all.accept_redirects = 0# 禁止icmp源路由net.ipv4.conf.all.accept_source_route = 0net.core.somaxconn = 65535net.core.rmem_default = 8388608net.core.wmem_default = 8388608net.core.rmem_max = 16777216net.core.wmem_max = 16777216net.core.netdev_max_backlog = 262144vm.swappiness = 3vm.overcommit_memory = 1vm.max_map_count = 262144[root@ansible roles]# cat setup-system/files/profile # 环境变量文件# /etc/profile# System wide environment and startup programs, for login setup# Functions and aliases go in /etc/bashrc# It's NOT a good idea to change this file unless you know what you# are doing. It's much better to create a custom.sh shell script in# /etc/profile.d/ to make custom changes to your environment, as this# will prevent the need for merging in future updates.pathmunge () {case ":${PATH}:" in*:"$1":*);;*)if [ "$2" = "after" ] ; thenPATH=$PATH:$1elsePATH=$1:$PATHfiesac}if [ -x /usr/bin/id ]; thenif [ -z "$EUID" ]; then# ksh workaroundEUID=`/usr/bin/id -u`UID=`/usr/bin/id -ru`fiUSER="`/usr/bin/id -un`"LOGNAME=$USERMAIL="/var/spool/mail/$USER"fi# Path manipulationif [ "$EUID" = "0" ]; thenpathmunge /usr/sbinpathmunge /usr/local/sbinelsepathmunge /usr/local/sbin afterpathmunge /usr/sbin afterfiHOSTNAME=`/usr/bin/hostname 2>/dev/null`HISTSIZE=1000if [ "$HISTCONTROL" = "ignorespace" ] ; thenexport HISTCONTROL=ignorebothelseexport HISTCONTROL=ignoredupsfiexport PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL# By default, we want umask to get set. This sets it for login shell# Current threshold for system reserved uid/gids is 200# You could check uidgid reservation validity in# /usr/share/doc/setup-*/uidgid fileif [ $UID -gt 199 ] && [ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]; thenumask 002elseumask 022fifor i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; doif [ -r "$i" ]; thenif [ "${-#*i}" != "$-" ]; then. "$i"else. "$i" >/dev/nullfifidoneunset iunset -f pathmungePS1='[\[\e[36m\]\#::\[\e[31m\]\u@\[\e[32m\]\h\[\e[36m\]::\w]\$ >>>\[\e[m\]'[root@ansible roles]# cat setup-system/files/ctn # ctn文件#!/usr/bin/env bashif [ $# -ne 1 ];thenecho -e "Usage: $0 {Container_name | Container ID}"exit 1fidocker exec -it $1 /bin/bash[root@ansible roles]# cat setup-system/files/rectn # rectn文件#!/usr/bin/env bashif [ $# -ge 2 ] ; thenFILTER="$1"shiftCMD="$@"elseecho '请检查参数'exit 1fiif docker ps --format '{{.Names}}' --filter name=$FILTER &> /dev/null; thenC_LIST=$(docker ps --format '{{.Names}}' --filter name=$FILTER)for C_NAME in $C_LIST; doecho "---- ${C_NAME}:"docker exec $C_NAME $CMDdoneelseecho '找不到容器'exit 2fi[root@ansible roles]# cat setup-system/tasks/main.yml # 任务列表---# 禁用Ctrl-Alt-Delete组合键- name: disable Ctrl-Alt-Deletefile: src=/dev/null dest=/etc/systemd/system/ctrl-alt-del.target state=link# 内核参数优化- name: sysctl_configcopy: src=sysctl.conf dest=/etc/sysctl.conf mode=0644notify:- sysctl-load# 记录所有shell命令的执行历史...- name: copy bashrccopy: src=bashrc dest=/etc/bashrc mode=0644notify:- source bashrc# 添加 PS1 提示- name: copy profilecopy: src=profile dest=/etc/profile mode=0644notify:- source profile# 增加打开最大文件描述符...- name: copy limits.confcopy: src=limits.conf dest=/etc/security/limits.conf mode=0644# 时间同步服务器开启- name: Ensure chronyd is started and enabled at boot.systemd:name: chronydstate: startedenabled: yes# 取消容器redis的警告- name: "临时取消透明大页警告,不用重启"shell: echo never > /sys/kernel/mm/transparent_hugepage/enabledignore_errors: True# 永久生效:redis透明大页- name: "永久取消透明大页警告,重启生效"copy: src=rc.local dest=/etc/rc.local[root@ansible roles]# cat setup-system/handlers/main.yml # 触发任务---- name: sysctl-loadshell: sudo sysctl --system- name: source bashrcshell: source /etc/bashrc- name: source profileshell: source /etc/profile- name: source ctnshell: source /etc/bash_completion.d/ctn
至此,相关的roles文件就展示的差不多了。
执行脚本程序
[root@ansible ansible-init-system]# ./ansible-init.sh -f szy_init_system.yml -u root -p 22 -h node
执行过程如下:

既然都看到这了,那我就把该博文所涉及到的文件打个包供你们下载(提取码:kj4g)吧!
