跳到内容
观澜涉川 · 札记EN

使用 Ansible 部署 Dokku

· 约 6 分钟阅读

Dokku 是一个面向开发者的单节点平台即服务(PaaS),用于托管小型站点,它由 Bash 构建,并使用 Docker 作为容器。如果有过使用 Heroku 的经验,那么就会发现 Dokku 与 Heroku 的使用体验是如此相似。

本文以在互联网的服务器上安装 Dokku 为例,介绍 Ansible 的使用。

Ansible 简介

Ansible 是一个无代理的自动化配置管理工具。它通过 SSH 连接到目标服务器,执行各种脚本。可以用于服务器配置管理、应用部署、任务自动化等。Ansible 还拥有丰富的生态,很多开源的第三方模块、插件和扩展等,极大地简化了配置工作。Ansible 最初由 Michael DeHaan 编写,后来红帽(Red Hat)在2015年收购了它。

Ansible 相关文档

Ansible Galaxy

Ansible Galaxy 是一个中心化的资源平台,用于共享、发现和使用 Ansible 资源,包括“角色”、“集合”等。简单来说,用户可以将可复用的 Ansibile 配置发布到 Galaxy,然后可以使用 ansible-galaxy 命令使用它们。

相关链接:

Dokku 简介

Dokku 的相关链接如下:

使用 Ansible 安装 Dokku

创建云服务器

Dokku 的文档提到,服务器至少需要 1GB 的内存。如果小于 1GB,那么一个变通方法是增加服务器上的 swap 文件。

在服务器上创建 Debian 系统的服务器。可以使用 Vultr、DigitalOcean 等云服务商等产品。

将域名指向云服务器

修改域名的 DSN 配置,添加 A记录。使得域名指向新创建的服务器。

在下面的例子中,以 apps.domain.tld 作为域名示例。注意可以使用一级域名,也可以使用二级域名等。

在本机安装 Ansible

Ansible 的安装文档花了很大的篇幅详尽地介绍了不同的安装方式。不过如果熟悉 Python 开发,那么其实很简单。因为 Ansible 就是由 Python 编写的。

Python并没有一个很好的管理 Python 环境的官方工具。Ansible的文档提到了 pipx,但这似乎要安装很多东西,也许直接使用 pip 和 virtualenv 就可以。

我尝试了一个用于 Python 项目开发和包管理的工具 rye。它是有 Rust 编写的,只有一个可执行文件,非常地小巧和绿色。

在安装好了 rye 之后,参考它的基本用法来使用。首先,创建代码仓库:

Terminal window
rye init setup-dokku
cd setup-dokku

然后进行首次同步:

Terminal window
rye sync

安装 Ansible:

Terminal window
rye add ansible

接下来,进入虚拟环境:

Terminal window
. .venv/bin/activate

创建主机清单(Inventory)

Inventory 是 Ansible 用于定义和组织要管理的主机的列表。它可以包含服务器的 IP 地址、主机名或者组名等信息。可使用 ini 或者 yaml 格式编写。

比如使用 yaml 格式创建 inventory.yaml:

dokku:
hosts:
apps.domain.tld:

检查主机清单是否合法:

Terminal window
ansible-inventory -i inventory.yaml --list

ping一下主机,确认 SSH 连接正确。默认情况下,连接服务器时使用的是当前的用户名:

Terminal window
ansible dokku -m ping -i inventory.yaml

如果服务器的登录名与当前用户名不同,可使用 -u 选项指定登录名。比如指定登录名为 root:

Terminal window
ansible dokku -m ping -u root -i inventory.yaml

也可以在 inventory.yaml 里通过变量设置登录用户名,比如:

dokku:
hosts:
apps.domain.tld:
vars:
ansible_user: root

如果 ping 成功,那么控制台会显示:

apps.domain.tld | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.11"
},
"changed": false,
"ping": "pong"
}

安装 ansible_dokku

参考 Ansbile Galaxy 上的dokku_bot.ansible_dokku 页面,使用ansible-galaxy 命令即可安装角色:

Terminal window
ansible-galaxy role install dokku_bot.ansible_dokku

角色的代码默认被安装在 ~/.ansible/roles/ 文件夹下。

使用 HTTP 代理运行 ansible-galaxy

在实际操作中,如果当前位于中国大陆的网络,而服务器位于海外且网络连接不稳定,那么执行 ansible-galaxy 命令很可能连接超时……因此执行该命令时,需要使用服务器的网络工具。

如果使用通过代理服务器连接外网,那么需要注意:ansible-galaxy 命令目前似乎不支持 socks5 代理。因为在 socks 服务器上看到了错误:unknown Socks version: 67。此时,可以在本地开启 HTTP 代理(假定 HTTP 代理服务器端口为 10809),并且命令行上设置环境变量:

Terminal window
unset ALL_PROXY && unset http_proxy && unset https_proxy
export http_proxy=http://127.0.0.1:10809

然后执行上面的 ansible-galaxy 命令即可。

注:Ansible 的代码库上有一个添加 socks 代理支持的 PR ,目前还没有被合并。

使用剧本 (Playbook)安装 Dokku

创建剧本文件,playbook.yaml:

- hosts: dokku
roles:
- dokku_bot.ansible_dokku
vars:
dokku_plugins:
- name: postgres
url: https://github.com/dokku/dokku-postgres.git

然后执行剧本:

Terminal window
ansible-playbook -i inventory.yaml -u root playbook.yaml

有些服务器上有用户防火墙,可能只允许访问 22 端口。那么可以使用 ufw 打开 80 和 443 端口,或者在 Playbook 中使用 community.general.ufw 设置。比如

- hosts: dokku
roles:
- dokku_bot.ansible_dokku
vars:
dokku_plugins:
- name: postgres
url: https://github.com/dokku/dokku-postgres.git
tasks:
- name: Allow port 80
community.general.ufw:
rule: allow
port: 80
proto: tcp
- name: Allow port 443
community.general.ufw:
rule: allow
port: 443
proto: tcp

ansible-playbook 命令执行成功后,Dukko 也就安装成功了。接下来就可以在 Dukko 上部署应用了。

在执行 ansible-playbook 时使用代理

在执行 ansible-playbook 时,也发现一个问题:当访问海外主机时,网络通常不稳定;而 Ansible 使用的 SSH 连接也会变得不稳定,访问异常地慢。似乎 Ansible 会针对每个 Task 使用新的 SSH 连接(这可能是出于安全考虑)。所以从中国大陆使用 ansible-playbook 部署 Dokku 是一个缓慢和煎熬的过程……此时,可以通过修改 Ansible 所使用的 SSH 命令的参数来设置代理。

比如本机服务器的 10808 端口有 socks5 代理,那么可以修改 ansible_ssh_common_args 参数。比如在主机清单文件 inventory.yaml 中设置参数:

dokku:
# ...
vars:
ansible_ssh_common_args: -o ProxyCommand="/usr/bin/nc -X 5 -x 127.0.0.1:10808 %h %p"

ansible_ssh_common_args 会被追加到 ssh、scp、sftp 命令。有关 SSH 连接的更多介绍见文档“Connecting to hosts: behavioral inventory parameters”。

同时,在运行 ansible-playbook 时,可以设置详细输出,以便观察程序底层在做什么。可通过修改环境变量 ANSIBLE_VERBOSITY 的值(默认为 0;取值范围为 1|2|3|4);也可以给命令传入多个 v 参数,比如:

Terminal window
ansible-playbook -i inventory.yaml -u root playbook.yaml -vvv

登录配置

在安装好 Dokku 之后,通过 SSH 登录服务器时,服务器会给出提示:

! Setup a user's ssh key for deployment by passing in the public ssh key as shown:
echo 'CONTENTS_OF_ID_RSA_PUB_FILE' | dokku ssh-keys:add admin

如果仔细查看服务器,就会发现服务器已经创建了 dokku 用户,以及它的用户主目录 /home/dokku/。但此时 /home/dokku/.ssh/authorized_keys 的内容是空的。所以无法登录该用户。此时需要给 dokku 用户创建管理员的登录密钥,即执行上面提示里的命令:

Terminal window
echo 'CONTENTS_OF_ID_RSA_PUB_FILE' | dokku ssh-keys:add admin

执行成功后,就会发现 /home/dokku/.ssh/authorized_keys 里边已经有了登录用户的公钥。

这个时候,就可以部署应用了。

清理 不需要的 Docker 镜像

在 Dokku 上部署了多次应用后,可能会残留不需要的 Docker 镜像等资源,那么可以在系统中添加定时任务,清理 Docker。比如,每天凌晨执行 docker system prune -a -f。那么可以使用 crontab -e 打开定时任务编辑器,添加如下内容:

0 0 * * * docker system prune -a -f

结尾

使用 Ansible,可以自动化、反复地配置服务器;利用 Ansible Galaxy,可以利用模块化的配置,简化操作。就我个人的使用体验来说,使用 Ansible 的角色安装 Dokku 看起来只使用很少的配置,但是执行起来却是繁冗的(因为每次执行 ansible-playbook 都要检测服务器上的各个配置项的当前状态);如果遇到糟糕的网络状态,着实让人抓狂和放弃。因此,我的建议时使用 Ansible 和 ansible_dokku 用于完成最基本的服务器配置工作;对于具体的应用部署和配置,可使用 dokku 命令行程序,在服务器上执行。

附录

Dokku 的其他配置

在安装好了 Dokku 之后,还需要配置 SSH 密钥和虚拟主机。这些操作可以在服务器上通过执行 dokku 程序实现;也可以通过 Playbook 配置,以覆盖默认值。

比如,可以使用角色变量进行配置用户登录密钥;可使用库管理域名等。比如:

- hosts: dokku
roles:
- dokku_bot.ansible_dokku
vars:
dokku_key_file: ~/.ssh/dokku.pub # 登录 dokku 用户的公钥;这个公钥会被添加到 /home/dokku/.ssh/authorized_keys 中。注:实际中没有实验成功,似乎不生效?
dokku_hostname: apps.domain.tld # 默认为 dokku.me。用于虚拟主机域名和展示App的URL
dokku_plugins:
- name: postgres
url: https://github.com/dokku/dokku-postgres.git
- name: letsencrypt
url: https://github.com/dokku/dokku-letsencrypt.git
tasks:
- name: Allow port 80
community.general.ufw:
rule: allow
port: 80
proto: tcp
- name: Allow port 443
community.general.ufw:
rule: allow
port: 443
proto: tcp
- name: global domain
dokku_domains:
domains:
- apps.domain.tld # 全局的域名
global: true

(暂且还没搞懂 dokku_domains 里的全局域名与变量里的 dokku_hostname 有何区别与关系)