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