如何使用SaltCloudMap文件部署应用服务器和Nginx反向代理
介绍
您已经编写了应用程序,现在您需要部署它。 你可以创建一个生产环境并在虚拟机上设置你的应用程序,但是当它流行时你如何扩展它呢? 您如何推出新版本? 负载均衡呢? 而且,最重要的是,您如何确定配置是否正确? 我们可以自动化所有这些以节省大量时间。
在本教程中,我们将向您展示如何在 Salt Cloud 映射文件中定义您的应用程序,包括使用自定义 Salt grain 为您的服务器分配角色并动态配置反向代理。
在本教程结束时,您将拥有两个基本的应用程序服务器、一个具有动态构建配置的 Nginx 反向代理,以及在几分钟内扩展应用程序的能力。
先决条件
要遵循本教程,您将需要:
一个 1 GB CentOS 7 Droplet。 本教程中的所有命令都将以 root 身份执行,因此您无需创建 sudo 非 root 用户。
root 用户的 Droplet 上的 SSH 密钥(即 在 Droplet 上创建的新密钥对)。 在 DigitalOcean 控制面板中添加此 SSH 密钥,以便您可以使用它从主 Droplet 登录到其他 DigitalOcean Droplet。 您可以使用 How To Use SSH Keys with DigitalOcean Droplets 教程获取说明。
记下您在 DigitalOcean 控制面板中分配给键的名称。 在本教程中,我们使用名称 salt-master-root-key。 您还应该记下私钥的位置; 默认为
/root/.ssh/id_rsa
。个人访问令牌,您可以按照如何使用DigitalOcean APIv2的这一步中的说明创建。 确保将范围设置为读写。
第 1 步 — 安装 Salt 和 Salt Cloud
首先,您需要在服务器上安装和配置 Salt Cloud。 对于本教程,我们将只使用 Salt 引导脚本。
首先,蚀刻 Salt 引导脚本以安装 Salt。
wget -O install_salt.sh https://bootstrap.saltstack.com
运行 Salt 引导脚本。 我们使用 -M
标志来安装 salt-master
。
sh install_salt.sh -M
虽然 Salt Cloud 代码库已合并到核心 Salt 项目中,但它仍然为 CentOS 单独打包。 幸运的是,install_salt
脚本为我们配置了 repos,所以我们可以用 yum 安装 salt-cloud
:
yum install salt-cloud
现在我们可以检查 Salt Cloud 的版本以确认安装成功:
salt-cloud --version
你应该看到这样的输出:
salt-cloud --version 输出
salt-cloud 2015.5.3 (Lithium)
请注意,Salt 处于滚动发布周期,因此您的版本可能与上面略有不同。
第 2 步 — 配置盐云
在本节中,我们将配置 Salt Cloud 以连接到 DigitalOcean 并为我们的 Droplets 定义一些配置文件。
配置 DigitalOcean 提供程序文件
在 Salt Cloud 中, 提供程序文件 是您定义创建新 VM 的位置的位置。 提供程序定义在 /etc/salt/cloud.providers.d
目录中
使用 nano
或您喜欢的文本编辑器创建并打开 DigitalOcean 提供程序文件。
nano /etc/salt/cloud.providers.d/digital_ocean.conf
插入以下文本,将 red 中的变量替换为您的 - 即服务器 IP 和访问令牌,以及 SSH 密钥名称和文件(如果您自定义它们)。
/etc/salt/cloud.providers.d/digital_ocean.conf
### /etc/salt/cloud.providers.d/digital_ocean.conf ### ###################################################### do: provider: digital_ocean minion: master: your_server_ip # DigitalOcean Access Token personal_access_token: your_access_token # This is the name of your SSH key in your Digital Ocean account # as it appears in the control panel. ssh_key_name: salt-master-root-key # This is the path on disk to the private key for your Digital Ocean account ssh_key_file: /root/.ssh/id_rsa
您需要锁定 SSH 密钥文件的权限。 否则,SSH 将拒绝使用它。 假设您的位置位于默认位置 /root/.ssh/id_rsa
,您可以通过以下方式执行此操作:
chmod 600 /root/.ssh/id_rsa
为可部署服务器配置配置文件
在 Salt Cloud 中,profiles 是与提供者相关联的单个 VM 描述(例如 “DigitalOcean 上的 512 MB Ubuntu 虚拟机”)。 这些在 /etc/salt/cloud.profiles.d
目录中定义。
创建并打开配置文件。
nano /etc/salt/cloud.profiles.d/digital_ocean.conf
将以下内容粘贴到文件中。 无需修改:
/etc/salt/cloud.profiles.d/digital_ocean.conf
### /etc/salt/cloud.profiles.d/digital_ocean.conf ### ##################################################### ubuntu_512MB_ny3: provider: do image: ubuntu-14-04-x64 size: 512MB location: nyc3 private_networking: True ubuntu_1GB_ny3: provider: do image: ubuntu-14-04-x64 size: 1GB location: nyc3 private_networking: True
保存并关闭文件。 该文件定义了两个配置文件:
- 具有 512 MB 内存的 Ubuntu 14.04 VM,部署在纽约 3 区域。
- 具有 1 GB 内存的 Ubuntu 14.04 VM,部署在纽约 3 区域。
如果要使用 Ubuntu 14.04 以外的映像,可以使用 Salt Cloud 列出 DigitalOcean 上所有可用的映像名称,使用以下命令:
salt-cloud --list-images do
这将显示所有标准 DigitalOcean 图像,以及您使用快照工具保存在帐户中的自定义图像。 您可以使用此列表中的不同图像名称替换我们在提供程序文件中使用的图像名称或区域。 如果这样做,请确保在配置文件的 image 设置中使用此输出中的 slug 字段。
使用快速查询测试您的配置。
salt-cloud -Q
您应该会看到类似以下内容。
示例 salt-cloud -Q 输出
[INFO ] salt-cloud starting do: ---------- digital_ocean: ---------- centos-salt: ---------- id: 2806501 image_id: 6372108 public_ips: 192.241.247.229 size_id: 63 state: active
这意味着 Salt Cloud 正在与您的 DigitalOcean 帐户通信,并且您配置了两个基本配置文件。
第三步——编写一个简单的地图文件
映射文件是一个 YAML 文件,其中列出了您要创建的配置文件和服务器数量。 我们将从一个简单的地图文件开始,然后在下一节中构建它。
使用上述配置文件,假设您想要两个 1 GB 的应用服务器,前面有一个 512 MB 的反向代理。 我们将创建一个名为 /etc/salt/cloud.maps.d/do-app-with-rproxy.map
的地图文件并定义应用程序。
首先,创建文件:
nano /etc/salt/cloud.maps.d/do-app-with-rproxy.map
插入以下文本。 无需修改:
/etc/salt/cloud.maps.d/do-app-with-rproxy.map
### /etc/salt/cloud.maps.d/do-app-with-rproxy.map #### ###################################################### ubuntu_512MB_ny3: - nginx-rproxy ubuntu_1GB_ny3: - appserver-01 - appserver-02
就是这样! 这与地图文件一样简单。 继续部署这些服务器:
salt-cloud -m /etc/salt/cloud.maps.d/do-app-with-rproxy.map
命令完成后,通过快速 ping 确认成功:
salt '*' test.ping
您应该看到以下内容:
[label salt '*' test.ping appserver-01: True appserver-02: True nginx-rproxy: True
在映射文件中成功创建虚拟机后,删除它们同样简单:
salt-cloud -d -m /etc/salt/cloud.maps.d/do-app-with-rproxy.map
不过,请务必谨慎使用该命令! 它将删除 all 该映射文件中指定的 VM。
第四步——编写更真实的地图文件
该映射文件运行良好,但即使是 shell 脚本也可以启动一组虚拟机。 我们需要的是定义我们的应用程序的足迹。 让我们回到我们的地图文件并添加更多内容; 再次打开地图文件。
nano /etc/salt/cloud.maps.d/do-app-with-rproxy.map
删除文件的先前内容并将以下内容放入其中。 无需修改:
/etc/salt/cloud.maps.d/do-app-with-rproxy.map
### /etc/salt/cloud.maps.d/do-app-with-rproxy.map ### ##################################################### ubuntu_512MB_ny3: - nginx-rproxy: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: rproxy ubuntu_1GB_ny3: - appserver-01: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: appserver - appserver-02: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: appserver
现在我们到了某个地方! 它看起来很多,但我们只添加了两件事。 让我们回顾一下两个新增内容:mine_functions
部分和 grains
部分。
我们已经告诉 Salt Cloud 修改这些 VM 的 Salt Minion 配置并添加一些自定义 grains。 具体来说,grains 赋予反向代理 VM rproxy
角色,并赋予应用服务器 appserver
角色。 当我们需要动态配置反向代理时,这将派上用场。
mine_functions
也将添加到 Salt Minion 配置中。 它指示 Minion 将在 eth0 上找到的 IP 地址发送回 Salt Master 以存储在 Salt mine 中。 这意味着 Salt Master 将自动知道新创建的 Droplet 的 IP,而无需我们对其进行配置。 我们将在下一部分中使用它。
第五步——定义反向代理
我们现在有一个共同的任务摆在我们面前:安装反向代理 Web 服务器并进行配置。 在本教程中,我们将使用 Nginx 作为反向代理。
编写 Nginx Salt State
是时候动手编写一些 Salt 状态了。 首先,设置默认的 Salt 状态树位置:
mkdir /srv/salt
导航到该目录并为 nginx 再创建一个目录:
cd /srv/salt mkdir /srv/salt/nginx
进入该目录,使用您喜欢的编辑器,创建一个名为 rproxy.sls
的新文件:
cd /srv/salt/nginx nano /srv/salt/nginx/rproxy.sls
将以下内容放入该文件中。 无需修改:
/srv/salt/nginx/rproxy.sls
### /srv/salt/nginx/rproxy.sls ### ################################## ### Install Nginx and configure it as a reverse proxy, pulling the IPs of ### the app servers from the Salt Mine. nginx-rproxy: # Install Nginx pkg: - installed - name: nginx # Place a customized Nginx config file file: - managed - source: salt://nginx/files/awesome-app.conf.jin - name: /etc/nginx/conf.d/awesome-app.conf - template: jinja - require: - pkg: nginx-rproxy # Ensure Nginx is always running. # Restart Nginx if the config file changes. service: - running - enable: True - name: nginx - require: - pkg: nginx-rproxy - watch: - file: nginx-rproxy # Restart Nginx for the initial installation. cmd: - run - name: service nginx restart - require: - file: nginx-rproxy
此状态执行以下操作:
- 安装 Nginx。
- 将我们的自定义配置文件放入
/etc/nginx/conf.d/awesome-app.conf
。 - 确保 Nginx 正在运行。
我们的 Salt state 只是安装 Nginx 并删除一个配置文件; 真正有趣的内容在配置中。
编写 Nginx 反向代理配置文件
让我们为我们的配置文件再创建一个目录:
mkdir /srv/salt/nginx/files cd /srv/salt/nginx/files
并打开配置文件:
nano /srv/salt/nginx/files/awesome-app.conf.jin
将以下内容放入配置文件中。 无需修改,除非您是 不是 使用私有网络; 在这种情况下,将 1
更改为 0
,如行内所述:
/srv/salt/nginx/files/awesome-app.conf.jin
### /srv/salt/nginx/files/awesome-app.conf.jin ### ################################################## ### Configuration file for Nginx to act as a ### reverse proxy for an app farm. # Define the app servers that we're in front of. upstream awesome-app { {% for server, addrs in salt['mine.get']('roles:appserver', 'network.ip_addrs', expr_form='grain').items() %} server {{ addrs[0] }}:1337; {% endfor %} } # Forward all port 80 http traffic to our app farm, defined above as 'awesome-app'. server { listen 80; server_name {{ salt['network.ip_addrs']()[1] }}; # <-- change the '1' to '0' if you're not using # DigitalOcean's private networking. access_log /var/log/nginx/awesome-app.access.log; error_log /var/log/nginx/awesome-app.error.log; ## forward request to awesome-app ## location / { proxy_pass http://awesome-app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
我们使用 .jin
扩展名告诉自己该文件包含 Jinja 模板。 Jinja 模板允许我们将少量逻辑放入我们的文本文件中,以便我们可以动态生成配置详细信息。
此配置文件指示 Nginx 获取所有端口 80 HTTP 流量并将其转发到我们的应用程序场。 它有两部分:上游(我们的应用程序场)和充当用户和我们的应用程序场之间代理的配置。
先说上游吧。 一个普通的、非模板化的上游指定了一个 IP 集合。 但是,在我们的 minions 存在之前,我们不知道它们的 IP 地址是什么,并且我们不会手动编辑配置文件。 (否则,就没有理由使用 Salt!)
还记得我们地图文件中的 mine_function
行吗? 仆从将他们的 IP 提供给 Salt Master,以便在这种情况下存储它们。 让我们再仔细看看 Jinja 线:
神社节选
{% for server, addrs in salt['mine.get']('roles:appserver', 'network.ip_addrs', expr_form='grain').items() %}
这是 Jinja 中的 for 循环,运行任意 Salt 函数。 在这种情况下,它正在运行 mine.get。 参数是:
roles:appserver
- 这表示只从具有“appserver”角色的奴才那里获取详细信息。network.ip_addrs
- 这是我们想要从矿井中获取的数据。 我们也在地图文件中指定了这一点。expr_form='grain'
- 这告诉 Salt 我们正在根据他们的颗粒来瞄准我们的小兵。 更多关于在 the Saltstack 目标文档 中按粒度进行匹配的信息。
在这个循环之后,变量 模板:Addrs
包含一个 IP 地址列表(即使它只是一个地址)。 因为它是一个列表,所以我们必须用 [0]
来获取第一个元素。
那是上游。 至于服务器名称:
server_name {{ salt['network.ip_addrs']()[0] }};
这与 Salt mine 调用相同(在 Jinja 中调用 Salt 函数)。 它更简单。 它正在调用 network.ip_addrs 并获取返回列表的第一个元素。 这也让我们避免手动编辑我们的文件。
第六步——定义应用农场
如果反向代理背后没有应用程序,它就没有多大意义。 让我们创建一个小型 Node.js 应用程序,它只报告它所在服务器的 IP(因此我们可以确认我们正在访问两台机器)。
创建一个名为 awesome-app
的新目录并移动到那里。
mkdir -p /srv/salt/awesome-app cd /srv/salt/awesome-app
创建一个名为 app.sls
的新应用程序状态文件。
nano /srv/salt/awesome-app/app.sls
将以下内容放入文件中。 无需修改:
/srv/salt/awesome-app/app.sls
### /srv/salt/awesome-app/app.sls ### ##################################### ### Install Nodejs and start a simple ### web application that reports the server IP. install-app: # Install prerequisites pkg: - installed - names: - node - npm - nodejs-legacy # workaround for Debian systems # Place our Node code file: - managed - source: salt://awesome-app/files/app.js - name: /root/app.js # Install the package called 'forever' cmd: - run - name: npm install forever -g - require: - pkg: install-app run-app: # Use 'forever' to start the server cmd: - run - name: forever start app.js - cwd: /root
此状态文件执行以下操作:
- 安装
nodejs
、npm
和nodejs-legacy
软件包。 - 添加将成为我们的简单应用程序的 JavaScript 文件。
- 使用 NPM 安装 Forever。
- 运行应用程序。
现在创建(小)应用程序代码:
mkdir /srv/salt/awesome-app/files cd /srv/salt/awesome-app/files
创建文件:
nano /srv/salt/awesome-app/files/app.js
将以下内容放入其中。 无需修改:
/srv/salt/awesome-app/files/app.js
/* /srv/salt/awesome-app/files/app.js A simple Node.js web application that reports the server's IP. Shamefully stolen from StackOverflow: http://stackoverflow.com/questions/10750303/how-can-i-get-the-local-ip-address-in-node-js */ var os = require('os'); var http = require('http'); http.createServer(function (req, res) { var interfaces = os.networkInterfaces(); var addresses = []; for (k in interfaces) { for (k2 in interfaces[k]) { var address = interfaces[k][k2]; if (address.family == 'IPv4' && !address.internal) { addresses.push(address.address) } } } res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(JSON.stringify(addresses)); }).listen(1337, '0.0.0.0'); console.log('Server listening on port 1337');
这是一个简单的 Node.js 服务器,它只做一件事:在端口 1337 上接受 HTTP 请求并使用服务器的 IP 进行响应。
此时,您应该具有如下所示的文件结构:
文件结构
/srv/salt ├── awesome-app │ ├── app.sls │ └── files │ └── app.js └── nginx ├── rproxy.sls └── files └── awesome-app.conf.jin
第七步——部署应用程序
剩下的就是部署应用程序了。
使用 Salt Cloud 部署服务器
运行之前的相同部署命令,现在将使用我们所做的所有配置。
salt-cloud -m /etc/salt/cloud.maps.d/do-app-with-rproxy.map
等待盐云完成; 这需要一段时间。 返回后,通过 ping 应用服务器确认部署成功:
salt -G 'roles:appserver' test.ping
你应该看到:
应用服务器 ping 输出
appserver-02: True appserver-01: True
ping 反向代理:
salt -G 'roles:rproxy' test.ping
你应该看到:
反向代理 ping 输出
nginx-rproxy: True
一旦你有了你的虚拟机,就该让它们工作了。
构建应用程序
接下来,发出 Salt 命令以自动构建应用程序场和反向代理。
构建应用程序场:
salt -G 'roles:appserver' state.sls awesome-app.app
会有相当数量的输出,但应该以以下内容结束:
Summary ------------ Succeeded: 6 (changed=6) Failed: 0 ------------ Total states run: 6
构建反向代理:
salt -G 'roles:rproxy' state.sls nginx.rproxy
同样,将有相当数量的输出,以以下结尾:
Summary ------------ Succeeded: 4 (changed=4) Failed: 0 ------------ Total states run: 4
那么这里刚刚发生了什么?
第一个命令(带有应用服务器的命令)采用我们之前编写的 Salt 状态并在两个应用服务器上执行它。 这导致两台具有相同配置的机器运行相同版本的代码。
第二个命令(反向代理)执行我们为 Nginx 编写的 Salt 状态。 它安装了 Nginx 和配置文件,在配置文件中动态填写我们的应用程序场的 IP。
一旦这些 Salt 运行完成,您就可以进行测试以确认部署成功。 查找反向代理的 IP:
salt -G 'roles:rproxy' network.ip_addrs
如果您在 Droplet 上使用专用网络,您可能会取回两个 IP。
将公共 IP 插入浏览器并访问网页! 刷新几次以确认 Nginx 实际上是在您构建的两个应用服务器之间进行代理。 您应该看到 IP 发生变化,确认您确实连接到多个应用服务器。
如果刷新后仍获得相同的 IP,则可能是由于浏览器缓存所致。 您可以尝试使用 curl
来显示 Nginx 正在您的应用服务器之间进行代理。 多次运行此命令并观察输出:
curl http://ip-of-nginx-rproxy
我们可以更进一步,完全通过OverState自动化应用程序部署。 这将让我们构建一个命令来告诉 Salt 首先构建应用程序服务器,然后再继续构建反向代理,从而保证我们构建过程的顺序。
第八步——扩大规模(可选)
使用 Salt 的目的是自动化您的构建过程; 使用 Salt Cloud 和地图文件的目的是轻松扩展您的部署。 如果您想在部署中添加更多应用程序服务器(例如,另外两个),您可以更新您的地图文件,使其如下所示:
/etc/salt/cloud.maps.d/do-app-with-rproxy.map
### /etc/salt/cloud.maps.d/do-app-with-rproxy.map ### ##################################################### ubuntu_512MB_ny3: - nginx-rproxy: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: rproxy ubuntu_1GB_ny3: - appserver-01: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: appserver - appserver-02: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: appserver - appserver-03: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: appserver - appserver-04: minion: mine_functions: network.ip_addrs: interface: eth0 grains: roles: appserver
进行该更新后,您将重新运行步骤 6 中的 salt-cloud 命令和两个 salt 命令:
salt-cloud -m /etc/salt/cloud.maps.d/do-app-with-rproxy.map salt -G 'roles:appserver' state.sls awesome-app.app salt -G 'roles:rproxy' state.sls nginx.rproxy
现有服务器不会受到重复 Salt 运行的影响,新服务器将按照规范构建,Nginx 配置将更新以开始将流量路由到新的应用程序服务器。
结论
部署只报告服务器 IP 的应用程序不是很有用。 幸运的是,这种方法不仅限于 Node.js 应用程序。 Salt 不在乎你的应用程序是用什么语言编写的。
如果你想使用这个框架来部署你自己的应用程序,你只需要自动化在服务器上安装应用程序的任务(通过脚本或使用 Salt 状态)并替换我们的 awesome-app以您自己的自动化为例。
就像 Salt 自动化您的 Droplets 上的流程一样,Salt Cloud 自动化您的云上的流程。 享受!