使用 Ansible 部署 Dokku

2024-08-30#Linux#Ansible#Dokku#PaaS

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 系统的服务器。可以使用 VultrDigitalOcean 等云服务商等产品。

将域名指向云服务器 🔗

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

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

在本机安装 Ansible 🔗

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

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

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

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

rye init setup-dokku
cd setup-dokku

然后进行首次同步:

rye sync

安装 Ansible:

rye add ansible

接下来,进入虚拟环境:

. .venv/bin/activate

创建主机清单(Inventory) 🔗

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

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

dokku:
  hosts:
    apps.domain.tld:

检查主机清单是否合法:

ansible-inventory -i inventory.yaml --list

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

 ansible dokku -m ping -i inventory.yaml

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

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 命令即可安装角色:

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),并且命令行上设置环境变量:

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

然后执行剧本:

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

有些服务器上有用户防火墙,可能只允许访问 22 端口。那么可以使用 ufw 打开 80443 端口,或者在 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 会被追加到 sshscpsftp 命令。有关 SSH 连接的更多介绍见文档“Connecting to hosts: behavioral inventory parameters”

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

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 用户创建管理员的登录密钥,即执行上面提示里的命令:

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 都要检测服务器上的各个配置项的当前状态);如果遇到糟糕的网络状态,着实让人抓狂和放弃。因此,我的建议时使用 Ansibleansible_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 有何区别与关系)


加载中...