返回介绍

11.1 确定所需环境的内容

发布于 2024-01-21 17:11:03 字数 11382 浏览 0 评论 0 收藏 0

首先要从所需环境的内容入手。环境一旦开始使用就很难再进行大的改动,所以动手搭建前搞清所需环境的内容是十分重要的。

在熟悉搭建环境的流程之前,建议先摸索着尝试手动搭建。等到熟悉之后,再考虑结构选择和搭建过程自动化的问题。

利用 VirtualBox 和快照功能可以帮助我们快速确定所需环境的内容。感兴趣的读者请同时参考附录 A。

11.1.1 网络结构

许多服务是由多台服务器组合实现的,所以在探讨服务器内部的环境搭建之前,先要确定服务器的组成结构。

可用服务器数、预算、应用性能不同,所需的服务器结构也是千差万别。这里我们以图 11.1 所示的结构为例进行学习。

图 11.1 服务器结构

· LB:负载平衡器(nginx)

· AP1、2:应用服务器(django/gunicorn)

· DB:数据库(mysql)

· gateway:跳板服务器

◉ 服务器编组

确定服务器结构时,要给职责相同的服务器起一个统一的名字。

这里我们将分担各个职责的服务器群称为组。在图 11.1 所示的结构中,有 LB、AP、DB、gateway 这 4 个组。

即便某个职责只由一台服务器完成,我们也认为其是一个组。这样一来能更灵活地应对多服务器结构。

NOTE

按职责划分的服务器群也被称为“角色”(Role)。但这会与我们即将讲到的 Ansible 的 Role 重复,所以这里改用“组”(Group)。

◉ 跳板服务器

允许从外部网络直接 ssh 登录各个服务器会带来很多安全隐患,所以一般我们会阻止外部通过 ssh 访问各服务器。

可是,一旦阻止了对所有服务器的 ssh,我们便无法通过外网登录,这会造成很多不便。所以这里要准备一台用作登录跳板的服务器,形成通过跳板登录各服务器的结构。

11.1.2 服务器搭建内容的结构化

将服务器搭建内容按照图 11.2 所示的结构进行结构化。

图 11.2 服务器搭建内容的结构化

这个结构很好理解。在中间件里定义安装和设置等流程,然后将各个中间件组合起来搭建服务器组。最后把搭建好的多个服务器组整合起来,就形成了一个环境。

这里需要特别注意的是,将搭建流程整合到各个中间件里时,如果只简单地把要做的处理按顺序一件一件写下来,很容易让人搞不清究竟哪个步骤属于哪个中间件的设置。加强对这个结构的理解和注意,有助于提高自动化的效率。关于自动化的知识我们将在 11.2 节进行学习。

根据图 11.2 所示的结构,本章中的服务器可划分为如下图 11.3 所示的结构。

图 11.3 本章中的服务器的搭建内容

这里出现了“环境设置”,它不是中间件,而是我们为了便于理解,将 OS 的设置、用户的创建等不针对特定中间件进行的操作汇总在了一起,并统一称为“环境设置”。

下面我们开始学习具体的搭建流程。

11.1.3 用户的设置

我们已经确定好了服务器的结构,接下来要探讨搭建流程。首先是用户的设置。

初始状态下的 Ubuntu 以 ubuntu 用户为登录用户。但是,如果让默认的管理员用户来做维护或启动应用,会带来一些安全隐患。所以,我们新建 mainte 用户用于搭建和维护,新建一个不能直接登录的普通用户 www 用于执行应用。

# useradd -m mainte
# useradd -m www

搭建工作会经常用到 sudo,因此要给 mainte 用户设置 sudo 权限。另外,考虑到自动化的问题,最好让 mainte 不需要密码就可以执行 sudo。

可以通过编辑 /etc/sudoers 或者在 /etc/sudoers.d/ 中添加设置文件修改 sudo 权限。这次我们用后一种方法,创建 /etc/sudoers.d/mainte。

%mainte ALL=(ALL) NOPASSWD:ALL

%mainte 表示给 mainte 组的所有用户设置 sudo 权限。今后如果有其他用户需要用到 sudo,只要将其加入 mainte 组即可。

至于 www 用户,要让它只保有启动应用所需的最小权限,因此不设置 sudo 权限。

最后是登录设置。mainte 用户今后要用来做维护工作,所以需要能从外部登录。

首先用 ssh-keygen 生成密钥文件。

$ sudo su - mainte
$ ssh-keygen -b 2048

如果在初始设置状态下执行 ssh-keygen ,会在 /home/mainte/.ssh/ 下生成 id_rsa 和 id_rsa.pub 两个文件。我们将这两个文件下载到本地 PC 保管。

将 id_rsa.pub 的内容设置到 authorized_keys 中,登录设置就完成了。

$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

经过上面的设置,我们在 mainte 用户的“.ssh”目录下生成了密钥文件,同时完成了对应的 authorized_key 的设置。

这样设置下来之后,就能很轻松地在多个服务器之间切换登录了。

最后查看设置是否正确。

· mainte 用户能用下载到本地 PC 的密钥文件登录服务器

· mainte 用户能通过 sudo 切换为 www 用户

· 可以对 localhost 执行 ssh

$ ssh localhost

11.1.4 选定程序包

接下来安装应用运行所需的程序包。

在 Ubuntu 上运行 Python 应用时,主要通过 apt-get 和 pip 安装程序包。

选择要安装的程序包时请注意以下几点。

① 安装的程序包要尽量少

尽量不要导入与运行应用无关的程序包。

盲目导入程序包会使我们无法准确掌握应用的运行条件,出问题时很难分辨问题出在环境上还是应用本身上。

要导入与运行应用没有直接关系的程序包时,必须先仔细考虑其用途和安装范围,再确定是否要将其加入搭建流程。

下面是一些做判断的例子。

· 应用的目录结构很复杂,排查 Bug 时需要 tree 命令的辅助

任何环境都难免需要排查 Bug,因此需要将 tree 命令加入构建流程。

· 想用自己常用的编辑器 emacs

基本只会在开发环境中用到,所以仅在开发环境中导入。

② 掌握并管理程序包的版本

对开发力度较大的应用 / 库而言,常有从某个版本起发生大幅度规格变更,或是不兼容旧版本设置文件的情况发生。

这种时候如果盲目升级了版本,很容易导致应用无法运行。

要防止这类因版本导致的事故,重点在于把握安装的程序包的版本以及确定更新原则。

原则主要有下面几种,各位可以为每个程序包分别选择合适的原则。

· 完全固定

不考虑版本升级,仅使用指定版本的程序包。

· 固定至次版本号

固定主版本号和次版本号,允许加入安全更新和 Bug 修复。

◉ 通过 apt-get 安装程序包

用 apt-get 或 yum 从各个 Linux 发行版的程序包版本库安装程序包时,首先要确认各个发行版的更新原则。

本书所用的 Ubuntu 原则上只加入安全更新和 Bug 修复,不进行其他版本升级。

也就是说,环境本身就处于上述“固定至次版本号”原则的状态,我们不必多作修改。程序包升级时只会加入重大的 Bug 修复,不会更新主版本号和次版本号。

上述原则只要能满足我们的需求即可,没有什么特别需要注意的地方。现在我们可以通过简单的命令安装程序包,具体如下。

$ sudo apt-get install packagename

如果需要固定版本,则需要在程序包名称后面添加等号以及版本号。具体如下所示。

$ sudo apt-get install packagename=1.2.3-4ubuntu3

包名和版本号都可以用正则表达式。比如,如果只想固定到次版本号,则上述命令可以写成“1.2\..*”的形式。

◉ 通过 pip 安装程序包

用 pip 安装程序包的流程在第 9 章中有详细介绍,不清楚的读者请参考该部分。

根据 11.1.4 节得出的结果编写 requirements.txt,修正版本指定,依照自身情况选择是否分离一部分到 dev-requires.txt 和 tests-require.txt。

完成 requirements.txt 之后,只需用 pip install -r requirements.txt 进行安装即可。

◉ 封闭环境中的安装

为没有网络连接的服务器搭建环境时,apt-get 和 pip 这种通过外部连接安装程序包的方法就不好用了。另外,程序包提供方(apt 版本库、PyPI、GitHub 等)故障或维护时也是同样道理。

为应对这种情况,我们可以将所需的程序包事先保存在版本库中,然后进行离线安装。我们把这些打包在一起的程序包叫作 bundle。

bundle 不但可以让我们离线安装程序包,还能有效固定版本以及削减搭建时间。

通过 apt-get 安装 bundle

apt-get 安装的程序包都是以“.deb”格式发布的。用 apt-get download 命令可以获取我们需要的 deb 程序包。

$ mkdir aptcache && cd aptcache
$ sudo apt-get download python3.4
$ ls
python3.4_3.4.0-2ubuntu1_amd64.deb

deb 程序包用 dpkg -i 命令安装。

$ sudo dpkg -i python3.4_3.4.0-2ubuntu1_amd64.deb

但是有一个问题,apt-get download 命令只能获取目标程序包,无法同时获取该程序包的依赖包。因此要用 apt-cache depends 命令查看依赖包,然后在通过上述方法获取它们的 deb 程序包。

$ apt-cache depends python3.4
python3.4
  Depends: python3.4-minimal
  Depends: libpython3.4-stdlib
  Depends: mime-support
  Suggests: python3.4-doc
  Suggests: binutils

有时这些依赖包本身又依赖于其他程序包,如此循环下去不知何时是个头,所以我们需要一个方法来一次性获取所有相关的程序包。

实际上,用 apt-get install 命令安装程序包时,目标程序包及其所有依赖包的 deb 文件全都会被下载到缓存目录下。只要通过 -o 选项临时变更缓存目录,就能将目标程序包和其所有依赖包保存到任意目录下了。

$ sudo apt-get install python3.4 -o Dir::Cache::Archives=/home/mainte/aptcache

然而有一点需要注意,那就是这个方法无法获取已经安装的程序包,因此需要在初始状态的环境下(比如刚用 VM 搭建完毕的环境)进行操作。

通过 pip 安装 bundle

旧版本的 pip 可以用 pip bundle 命令进行安装,但这个命令在 pip 1.5 被删除。1.5 之后开始使用 wheel。9.1.4 节详细介绍了 wheel 的相关内容,有需要的读者请参考该部分。

将所需程序包全部转换为 wheel。此时也通过 requirements.txt 指定程序包。

$ pip wheel -r requirments.txt

这个操作会在当前目录的 wheelhouse 目录下生成 wheel。我们将整个 wheelhouse 目录都添加到应用的版本库中。

在各环境下都可以通过下述命令从 wheelhouse 安装程序包。

$ pip install --no-index -f /path/to/my/repository/wheelhouse -r requirements.txt

bundle 的维护

更新或添加新的程序包时,需要将程序包重新打包成 bundle。一旦漏掉这个步骤,应用可能出现在某些环境下能运行,在某些环境下却不能运行的情况,问题很难排查。

重新打包 bundle 是一件非常繁琐的工作,对于 deb 程序包来说尤其如此。开发时会频繁出现程序包的添加和更新,因此过早地 bundle 化会带来许多麻烦。毕竟开发环境很少遇到无法连接外部网络的情况,所以开发中建议使用 apt-get 和 pip install 来安装程序包。等到正式环境就绪再考虑 bundle 化也不迟。

程序包管理进入 bundle 阶段后,程序包的 bundle 化也可以交给构建服务器实现自动化。

11.1.5 中间件的设置

mysql、nginx 等中间件要根据使用环境进行设置。

我们往往需要在大量服务器上实施或更新中间件的设置,如果这些全都手动去完成,很容易出现疏漏,导致发生问题。因此,实现中间件设置的自动化才是上上之选。首先搞清需要自动化的内容,选出对象文件以及要修改的项目。本章用作例子的服务器结构中包含了 mysql、nginx 和 gunicorn,这里我们就来探讨一下这 3 个中间件的设置。

◉ 让中间件设置生效的方法

自动化更新中间件的设置文件需要以下步骤。

① 在版本库中管理对象文件

② 用模板语言重新描述可变部分

③ 将模板化的文件翻译过来直接覆盖原设置文件

步骤②和③提到的模板功能是 Ansible 标配的功能,关于 Ansible 的知识会在后面讲到。探讨设置内容的过程中需要我们手动改写设置文件,但最终所有设置都要通过上述步骤反映出来。 Ansible 采用 Jinja2 为模板编辑器,因此本节的模板文件都是以 Jinja2 格式描述的。

下面我们来看看用 Jinja2 格式描述设置文件可变部分的例子。

◉ mysql

MySQL 的设置描述在“/etc/mysql/conf.d/*”中。本例的设置文件为 /etc/mysql/conf.d/myproject.cnf。

讨论所需项目并编写文件。

[mysqld]
bind_address = {{ MY_LOCAL_IP }}
innodb_file_per_table = yes
innodb_buffer_pool_size = {{ MYSQL_INNODB_BUFFER_POOL_SIZE }}

在 mysql 的设置中,有些值会根据环境变化,比如根据服务器 IP 变化的 bind_address,根据服务器内存容量变更最优值的 innodb_buffer_pool_size 等。如果事先将这些值设置为变量,可以在中间件变更所在服务器时轻松完成设置更新。

◉ nginx

nginx 的设置描述在 /etc/nginx/conf.d/ap.conf 中。

nginx 的 conf.d 目录下设置有 default.conf 和 example_ssl.conf 两个文件,它们是用来显示 nginx 默认页的,与我们要搭建环境的服务器无关。因此要在探讨设置之前把这两个文件删除。

LIST 11.1 /etc/nginx/conf.d/ap.conf

upstream app {
  {% for server in NGINX_UPSTREAMS %}
  server {{ server }}:8000;
  {% endfor %}
}
server {
  listen {% if SSL %}443{% else %}80{% endif %};
  server_name {{ DOMAIN }};
  {% if SSL %}
  ssl on;
  ssl_certificate {{ SSL.CERTIFICATE }};
  ssl_certificate_key {{ SSL.KEY }};
  {% endif %}
  ...
}

需要在 upstream 指令中指定多个反向代理对象的服务器地址。这里我们是用变量 NGINX_UPSTREAMS 来存储服务器地址清单,并通过 for 语句来指定服务器地址的。

另外,我们还对 server 指令内是否存在 SSL 的设置进行了判断,并根据情况进行了 listen 端口的切换以及 ssl_certificate 的设置。出于预算原因,我们的项目并不是每个环境都使用 SSL,所以在这里保证了两种设定之间的简单切换。

可见,利用 Jinja2 模板的 if 和 for 能做出复杂的情况分类(LIST 11.1)。但有一点需要注意,过度使用控制语句会降低程序的可读性,增加修改的难度。

◉ gunicorn

让 gunicorn 通过 upstart 启动。创建 /ect/init/myapp.conf 文件用作启动脚本,脚本内容如下。

upstart 的文档

http://upstart.ubuntu.com/

description "myapp"
start on (filesystem)
stop on runlevel [016]
respawn
console log
setuid www
setgid www
chdir {{ SOURCE_DIR }}/src
exec DJANGO_SETTINGS_MODULE={{ DJANGO_SETTINGS }} {{ SOURCE_DIR }}.venv/bin/gunicorn myapp.wsgi:application --bind={{ MY_LOCAL_IP }}:8000 --workers={{GUNICORN_WORKERS }}

上述例子将许多值替换为了变量。等到自动化的时候,这些地方就会发挥出其效果了。

· SOURCE_DIR

放置源码目录。虽然这个值很少因为环境而变,但换成变量能防止键入错误。

· DJANGO_SETTINGS

Django的settings文件需要根据环境而变。将它设置为变量之后,我们就可以在所有环境中应用同一个模板了。

· MY_LOCAL_IP

自身服务器的本地IP。直接拿mysql设置文件中定义的变量来用。

· GUNICORN_WORKERS

gunicorn的worker进程数。

将中间件的设置文件模板化时,需要考虑以下几个问题。

· 哪个设置值需要用变量替换

因环境而变的值,以及容易输入错的值(比如较长的文件路径)。

· 有多个中间件共用的变量吗

所有环境共享的值要设计成共用一个变量。

11.1.6 部署

部署就是将应用安置到环境中,使其进入可运行状态。

应用源码的安置、中间件设置的反映、中间件的重启等都属于部署工作。

本节将对上述 3 个工作进行说明。

◉ 源码的安置与更新

源码的安置用 Mercurial 的 clone 和 pull 即可轻松完成。利用标签和分支的功能,还能灵活应对回滚等操作。

如果有从各环境均可连接的公共密钥认证的中央版本库,那么最好从该版本库进行 clone 操作。这里我们假设中央版本库放在 myrepository.com。

clone 时用 11.1.3 节创建的 www 用户。访问版本库时用到的公共密钥和私有密钥要事先设置好。

本例中,我们把版本库设置在 /var/www/myproject。

$ cd /var/www/
$ hg clone ssh://myrepository.com/myproject

执行 clone 操作之后就完成了应用源码的安置工作。更新工作只需要在各个服务器上对版本库进行 pull/update 即可。

$ cd /var/www/myproject
$ hg pull; hg update default

根据用途和环境不同可以将 update 位置替换为其他分支名或标签名,以灵活应对各种需求。实现自动化时只需将其改为变量,让其能够被替换即可。

专栏 没有可通过公共密钥认证的中央版本库时该怎么办

源码在 Mercurial 上管理时,只要我们能使用 clone 操作,就能将源码部署到环境中。但是如果中央版本库要求密码认证,那么每次 clone 都要输入密码,不利于自动化。

这种时候可以先将主版本库 clone 到 gateway 服务器上,然后各环境服务器再从 gateway服务器上的版本库进行clone,这样就能将输入密码的次数降到最少(图11.4)。

图 11.4 密码认证环境中的源码部署

◉ 中间件设置的反映

如 11.1.5 节所述,现阶段需要手动改写模板部分并上传至各环境,从而反映设置。

改写过程中要时刻注意设置文件有没有放错地方,有没有不小心改写了未加入管理的文件。

◉ 重启中间件

守护进程的启动和停止要使用 service 命令。

$ sudo service myapp restart

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文