介绍
一个 Linux 容器是由使用 Linux 内核安全功能(如命名空间和控制组)与其他系统隔离的过程的组合,它类似于虚拟机,但更轻量级;你没有执行额外的内核或模拟硬件,这意味着你可以轻松地在同一个服务器上创建多个容器. 使用 Linux 容器,你可以运行整个操作系统的多个实例,限制在同一个服务器上,或将你的应用程序和其依赖组合成一个容器,而不会影响整个系统。
例如,假设您有一个服务器,并为客户端设置了多个服务,包括网站,在传统的安装中,每个网站都将是Apache或Nginx网页服务器相同实例的虚拟主机,但在Linux容器中,每个网页都将配置在自己的容器中,有自己的网页服务器。
我们可以使用 LXD来创建和管理这些容器。
在本教程中,您将使用LXD在相同的服务器上安装两个基于 Nginx的网站,每一个都限制在自己的容器上。然后您将安装HAProxy在第三个容器中,该容器将作为反向代理程序。
前提条件
要完成本教程,您将需要以下内容:
- 一个 Ubuntu 16.04 服务器,根据教程 Initial Server Setup with Ubuntu 16.04配置,具有 sudo 非根用户和防火墙。
- 两个完全合格的域名(FQDNs),每个 DNS A记录指向您的服务器的 IP 地址。 为了配置,请遵循教程 How To Set Up A Host Name with DigitalOcean。
- 可选地,根据教程 Getting Started with DigitalOcean Block Storage添加 20 GB 或更多区块存储空间。
步骤 1 – 将用户添加到lxd
组
使用非根用户帐户登录到服务器. 我们将使用非根用户帐户来执行所有容器管理任务. 要做到这一点,您必须先将该用户添加到lxd
组中。
1sudo usermod --append --groups lxd sammy
退出服务器并再次登录,以便您的新的 SSH 会话将与新组成员更新。
步骤 2 – 配置 LXD
LXD 需要正确配置才能使用它 最重要的配置决定是存储容器的存储备份类型 LXD 推荐的存储备份是 ZFS 文件系统,要么存储在预分配的文件中,要么使用 Block Storage。
1sudo apt-get update
2sudo apt-get install zfsutils-linux
安装后,您已经准备好初始化 LXD. 在初始化过程中,您将被要求指定 ZFS 存储后端的详细信息. 以下有两个部分,取决于您是否想要使用预分配的文件或区块存储。
选择1 – 使用预分配的文件
按照以下步骤配置 LXD 以使用预分配文件存储容器. 首先,执行以下命令开始 LXD 初始化过程:
1sudo lxd init
我们将选择所有默认值,包括预分配文件的建议大小,称为 loop 设备:
1[secondary_label Output]
2Name of the storage backend to use (dir or zfs) [default=zfs]: zfs
3Create a new ZFS pool (yes/no) [default=yes]? yes
4Name of the new ZFS pool [default=lxd]: lxd
5Would you like to use an existing block device (yes/no) [default=no]? no
6Size in GB of the new loop device (1GB minimum) [default=15]: 15
7Would you like LXD to be available over the network (yes/no) [default=no]? no
8Do you want to configure the LXD bridge (yes/no) [default=yes]? yes
9Warning: Stopping lxd.service, but it can still be activated by:
10 lxd.socket
11LXD has been successfully configured.
建议的大小从您的服务器的可用磁盘空间自动计算。
一旦设备已配置,您将配置网络设置,我们将在下一个可选部分之后探索。
选择2 – 使用区块存储
如果您要使用区块存储,您需要找到指向您创建的区块存储量的设备,以便在 LXD 配置中指定它。
通过查看命令来定义音量来查找设备。 具体来说,请寻找在sudo mkfs.ext4 -F
命令中指定的路径。 下图显示了音量的一个示例。
在这种情况下,卷名为 /dev/disk/by-id/scsi-0D0_Volume_volume-fra1-01
,尽管您的名称可能不同。
一旦您确定了音量,请返回终端并发出以下命令,开始LXD初始化过程。
1sudo lxd init
您将被介绍一系列问题. 如下输出所示,回答问题:
1[secondary_label Output]
2Name of the storage backend to use (dir or zfs) [default=zfs]: zfs
3Create a new ZFS pool (yes/no) [default=yes]? yes
4Name of the new ZFS pool [default=lxd]: lxd
当您被提示使用现有区块设备时,选择是
并提供到您的设备的路径:
1[secondary_label Output of the "lxd init" command]
2Would you like to use an existing block device (yes/no) [default=no]? yes
3Path to the existing block device: /dev/disk/by-id/scsi-0DO_Volume_volume-fra1-01
然后将默认值用于剩余的问题:
1[secondary_label Output of the "lxd init" command]
2Would you like LXD to be available over the network (yes/no) [default=no]? no
3Do you want to configure the LXD bridge (yes/no) [default=yes]? yes
4Warning: Stopping lxd.service, but it can still be activated by:
5 lxd.socket
6LXD has been successfully configured.
一旦该过程完成,您将配置网络。
配置网络
初始化过程将向我们展示一系列如下图所示的屏幕,允许我们为容器配置网络桥梁,以便他们可以获得私人IP地址,彼此通信,并访问互联网。
使用每个选项的默认值,但当被问及IPv6网络时,请选择 No,因为我们不会在本教程中使用它。
一旦您完成了网络配置,您已经准备好创建容器。
步骤三:创建集装箱
我们已经成功配置了 LXD. 我们已经指定了存储后端的位置,并为任何新创建的容器配置了默认网络。
让我们尝试我们的第一个命令,其中列出了可用的安装容器:
1lxc list
您将看到以下输出:
1[secondary_label Output of the "lxd list" command]
2Generating a client certificate. This may take a minute...
3If this is your first time using LXD, you should also run: sudo lxd init
4To start your first container, try: lxc launch ubuntu:16.04
5
6+------+-------+------+------+------+-----------+
7| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
8+------+-------+------+------+------+-----------+
由于这是lxc
命令首次与LXD超视图进行通信,所以输出告诉我们,该命令会自动创建一个客户端证书,以便与LXD进行安全的通信。
让我们创建三个容器,我们将为每个Web服务器创建一个,并为反向代理商创建第三个容器。
我们将使用lxc launch
命令创建并启动一个名为web1
的Ubuntu 16.04(ubuntu:x
)容器。在ubuntu:x
中的x
是Xenial的第一个字母的缩写,Ubuntu 16.04的代码名称ubuntu:
是LXD图像预配置库的标识符。
<$>[注]
**注:您可以通过运行lxc 图像列表 ubuntu:
和其他分布来找到所有可用的 Ubuntu 图像的完整列表,并通过运行lxc 图像列表图像:
。
执行以下命令来创建容器:
1lxc launch ubuntu:x web1
2lxc launch ubuntu:x web2
3lxc launch ubuntu:x haproxy
因为这是我们第一次创建一个集装箱,所以第一个命令会从互联网下载集装箱图像并将其本地缓存。
在这里,您可以看到创建容器web1
的样本输出。
1[secondary_label Output]
2Creating web1
3Retrieving image: 100%
4Starting web1
现在我们已经创建了三个空的罐,让我们使用lxc列表
命令来显示有关它们的信息:
1lxc list
输出显示了每个容器的名称、当前状态、IP地址、类型以及是否有截图。
1[label Output]
2+---------+---------+-----------------------+------+------------+-----------+
3| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
4+---------+---------+-----------------------+------+------------+-----------+
5| haproxy | RUNNING | 10.10.10.10 (eth0) | | PERSISTENT | 0 |
6+---------+---------+-----------------------+------+------------+-----------+
7| web1 | RUNNING | 10.10.10.100 (eth0) | | PERSISTENT | 0 |
8+---------+---------+-----------------------+------+------------+-----------+
9| web2 | RUNNING | 10.10.10.200 (eth0) | | PERSISTENT | 0 |
10+---------+---------+-----------------------+------+------------+-----------+
请注意容器名称及其相应的IPv4地址,您将需要它们来配置您的服务。
步骤 4 – 配置 Nginx 容器
让我们连接到web1
容器并配置第一个Web服务器。
要连接,我们使用lxc exec
命令,该命令采用容器的名称和要执行的命令。
1lxc exec web1 -- sudo --login --user ubuntu
--
字符串表示lxc
的命令参数应该停留在那里,其余的行将作为在容器内部执行的命令传递。
<$>[注]
注: 如果您需要作为 root连接到容器,则可以使用命令 lxc exec web1 -- /bin/bash
。
一旦进入容器,我们的壳提示现在看起来如下。
1[secondary_label Output]
2[environment second]
3ubuntu@web1:~$
容器中的这个 ubuntu用户有预配置的sudo
访问,并且可以运行sudo
命令而无需提供密码。这个壳被限制在容器的边界内。
让我们更新容器内 Ubuntu 实例的包列表并安装 Nginx:
1[environment second]
2sudo apt-get update
3sudo apt-get install nginx
让我们编辑这个网站的默认网页,并添加一些文本,清楚地表明这个网站是在web1
容器中托管的。
1[environment second]
2sudo nano /var/www/html/index.nginx-debian.html
对文件进行以下更改:
1[label Edited file /var/www/html/index.nginx-debian.html]
2<!DOCTYPE html>
3<html>
4<head>
5<title>Welcome to nginx on LXD container web1!</title>
6<style>
7 body {
8 width: 35em;
9 margin: 0 auto;
10 font-family: Tahoma, Verdana, Arial, sans-serif;
11 }
12</style>
13</head>
14<body>
15<h1>Welcome to nginx on LXD container web1!</h1>
16<p>If you see this page, the nginx web server is successfully installed and
17working. Further configuration is required.</p>
18...
我们在两个地方编辑了该文件,并特别添加了文本LXD container web1
。
现在退出容器并返回主机服务器:
1[environment second]
2logout
对于web2
容器,重复此过程。登录,安装 Nginx,然后编辑文件/var/www/html/index.nginx-debian.html
以提到web2
。
让我们使用‘curl’来测试容器中的网页服务器是否正常工作,我们需要先前显示的网页容器的IP地址。
1curl http://10.10.10.100/
产量应该是:
1[secondary_label Output of "curl http://10.10.10.100/" command]
2<!DOCTYPE html>
3<html>
4<head>
5<title>Welcome to nginx on LXD container web1!</title>
6<style>
7 body {
8 width: 35em;
9 margin: 0 auto;
10 font-family: Tahoma, Verdana, Arial, sans-serif;
11 }
12</style>
13</head>
14<body>
15<h1>Welcome to nginx on LXD container web1!</h1>
16<p>If you see this page, the nginx web server is successfully installed and
17working. Further configuration is required.</p>
18...
测试第二个容器,使用弯曲
命令和其IP地址来验证它也设置正确. 随着两个容器配置,我们可以继续设置HAProxy。
步骤 5 – 配置 HAProxy 容器
我们将在这些容器前设置 HAProxy 作为代理程序。如果您需要更多关于此功能的背景,请参阅教程 HAProxy 和负载平衡概念的介绍。我们将根据我们使用的域名导向每个容器的流量。在下面的配置示例中,我们将使用域名example.com
,这是一个 专门保留域作为本教程的文档。我们将把第一个网站提供给主机名称 example.com
和 www.example.com
。第二个网站将位于 www2.example.com
。 取代您的域名而不是这些域名。
登入「haproxy」容器:
1lxc exec haproxy -- sudo --login --user ubuntu
更新安装包列表并安装 HAProxy:
1[environment fourth]
2sudo apt-get update
3sudo apt-get install haproxy
一旦安装完成,我们可以配置 HAProxy. HAProxy 的配置文件位于 /etc/haproxy/haproxy.cfg
。 使用您最喜欢的文本编辑器打开该文件。
1[environment fourth]
2sudo nano /etc/haproxy/haproxy.cfg
我们将添加forwardfor
选项,以便我们保留Web客户端的真实源IP,我们将添加http-server-close
选项,这允许重新使用会话和更低的延迟。
1[environment fourth]
2[label /etc/haproxy/haproxy.conf]
3global
4...
5defaults
6 log global
7 mode http
8 option httplog
9 option dontlognull
10 option forwardfor
11 option http-server-close
12 timeout connect 5000
13 timeout client 50000
14 timeout server 50000
15...
接下来,我们将配置前端以指向我们的两个后端容器. 添加一个名为www_frontend
的新前端
部分,看起来像这样:
1[environment fourth]
2[label /etc/haproxy/haproxy.conf]
3frontend www_frontend
4 bind *:80 # Bind to port 80 (www) on the container
5
6 # It matches if the HTTP Host: field mentions any of the hostnames (after the '-i').
7 acl host_web1 hdr(host) -i example.com www.example.com
8 acl host_web2 hdr(host) -i web2.example.com
9
10 # Redirect the connection to the proper server cluster, depending on the match.
11 use_backend web1_cluster if host_web1
12 use_backend web2_cluster if host_web2
acl
命令匹配 Web 服务器的主机名称,并将请求重定向到相应的后端
部分。
然后我们定义了两个新的后端
部分,一个为每个Web服务器,并分别命名它们为web1_cluster
和web2_cluster
。
1[environment fourth]
2[label /etc/haproxy/haproxy.conf]
3backend web1_cluster
4 balance leastconn
5 # We set the X-Client-IP HTTP header. This is useful if we want the web server to know the real client IP.
6 http-request set-header X-Client-IP %[src]
7 # This backend, named here "web1", directs to container "web1.lxd" (hostname).
8 server web1 web1.lxd:80 check
9
10backend web2_cluster
11 balance leastconn
12 http-request set-header X-Client-IP %[src]
13 server web2 web2.lxd:80 check
平衡
选项表示负荷平衡策略. 在这种情况下,我们选择了最少的连接数量。 http-request
选项设置了一个HTTP标题与实际的Web客户端IP。如果我们没有设置这个标题,网络服务器将记录HAProxy IP地址作为所有连接的源IP,这使得分析您的流量来源更为困难。
LXD 為容器提供 DNS 伺服器,因此「web1.lxd」會解決與「web1」容器相關的 IP. 其他容器有自己的主機名稱,例如「web2.lxd」和「haproxy.lxd」。
检查
参数告诉HAPRoxy在Web服务器上进行健康检查,以确保它可用。
要测试配置是否有效,请运行以下命令:
1[environment fourth]
2/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c
产量应该是
1[environment fourth]
2[label Output]
3Configuration file is valid
让我们重新加载HAProxy,以便它读取新配置。
1[environment fourth]
2sudo systemctl reload haproxy
现在退出容器以返回主机。
1[environment fourth]
2logout
我们已经配置了 HAProxy 作为一个反向代理,将其在端口 `80 上接收的任何连接转发到其他两个容器中的相应 Web 服务器。
1curl --verbose --header 'Host: web2.example.com' http://10.10.10.10
这会向 HAProxy 提出请求,并设置一个 HTTP 主机
标题,HAProxy 应该使用它来重定向连接到相应的 Web 服务器。
产量应该是
1[secondary_label Output of "curl --verbose --header 'Host: web2.example.com' http://10.10.10.10" command]
2...
3> GET / HTTP/1.1
4> Host: web2.example.com
5> User-Agent: curl/7.47.0
6> Accept: */*
7>
8...
9<
10<!DOCTYPE html>
11<html>
12<head>
13<title>Welcome to nginx on LXD container web2!</title>
14<style>
15...
HAProxy正确理解了请求,并将其转发到web2
容器中,在那里,Web服务器服务于我们之前编辑的默认索引页面,并显示了文本在LXD容器上Web2
。
步骤 6 – 将接入连接传送到 HAProxy 容器
谜题的最后一部分是将反向代理连接到互联网,我们需要设置我们的服务器,将它可能从互联网接收的任何连接从端口80
传送到haproxy
容器。
HAProxy 安装在容器中,默认情况下是无法从互联网上访问的。 为了解决此问题,我们将创建一个iptables
规则来传输连接。
iptables
命令需要两个IP地址:服务器的公共IP地址(your_server_ip
)和haproxy
容器的私人IP地址(your_haproxy_ip
),您可以通过lxc列表
命令获取。
运行此命令来创建规则:
1sudo iptables -t nat -I PREROUTING -i eth0 -p TCP -d your_server_ip/32 --dport 80 -j DNAT --to-destination your_haproxy_ip:80
以下是如何打破命令:
-t nat
指明我们正在使用nat
表-I PREROUTING
指明我们正在将规则添加到 PREROUTING 链-i eth0
指明界面 eth0,这是Dropplets上的默认公共接口-p TCP
表示我们正在使用 TCP 协议-d your_server_ip/32
指明规则的目的地 IP 地址--dport 80
: 指明目的地端口-j DNAT
表示我们想要执行跳跃到目的地 NAT (DNAT).(_
了解有关 IPTables 的更多信息,请参阅 How the Iptables Firewall Works和 IPtables Essentials: Common Firewall Rules and Commands。
最后,为了保存这个iptables
命令,以便在重新启动后重新应用,我们安装了iptables-persistent
包:
1sudo apt-get install iptables-persistent
安装包时,您将被要求保存当前的 iptables 规则. 接受并保存所有当前的 'iptables' 规则。
如果您已经设置了两个FQDN,那么您应该能够使用您的Web浏览器连接到每个网站。
要测试这两个 Web 服务器是否实际上可以从 Internet 访问,请使用弯曲
命令从本地计算机访问它们:
1[environment local]
2curl --verbose --header 'Host: example.com' 'http://your_server_ip'
3curl --verbose --header 'Host: web2.example.com' 'http://your_server_ip'
这些命令将 HTTP 连接到服务器的公共 IP 地址,并添加一个 HTTP 标题字段与 `-- header' 选项,HAProxy 将用来处理请求,就像您在步骤 5 中所做的。
以下是第一个curl
命令的输出:
1[secondary_label Output]
2* Trying your_server_ip...
3* Connected to your_server_ip (your_server_ip) port 80 (#0)
4> GET / HTTP/1.1
5> Host: example.com
6> User-Agent: curl/7.47.0
7> Accept: */*
8>
9< HTTP/1.1 200 OK
10< Server: nginx/1.10.0 (Ubuntu)
11...
12<!DOCTYPE html>
13<html>
14<head>
15<title>Welcome to nginx on LXD container web1!</title>
16<style>
17 body {
18...
以下是第二个curl
命令的输出:
1[secondary_label Output]
2* Trying your_server_ip...
3* Connected to your_server_ip (your_server_ip) port 80 (#0)
4> GET / HTTP/1.1
5> Host: web2.example.com
6> User-Agent: curl/7.47.0
7> Accept: */*
8>
9< HTTP/1.1 200 OK
10< Server: nginx/1.10.0 (Ubuntu)
11...
12<!DOCTYPE html>
13<html>
14<head>
15<title>Welcome to nginx on LXD container web2!</title>
16<style>
17 body {
18...
在这两种情况下,正确的网站显示。
结论
您已经设置了两个网站,每一个在自己的容器中,HAProxy导向流量. 您可以复制此过程,以配置更多网站,每一个仅限于自己的容器。
您也可以在新容器中添加MySQL,然后安装像WordPress这样的CMS来运行每个网站. 您也可以使用这个过程来支持较旧的软件版本. 例如,如果安装CMS需要较旧的软件版本,如PHP5,那么您可以在容器中安装Ubuntu 14.04(lxc launch ubuntu:t
),而不是试图降级Ubuntu 16.04上可用的包管理器版本。
最后,LXD提供了拍摄集装箱完整状态的快照的能力,这使得更轻松地创建备份并在稍后重新滚动集装箱,此外,如果我们在两个不同的服务器上安装LXD,则可以连接它们并通过互联网迁移服务器之间的集装箱。