编程

使用 Ansible 和 GitHub Actions 部署你的 PHP 代码库

2191 2022-02-16 10:31:15

本文将展示如何使用 GitHub Actions 及 Ansible 自动部署你的 PHP 代码库。

在生产服务器上部署 PHP 应用有很多不同方式。如今,大部分项目使用 git 和像GitHub, GitLab 或 Bitbucket 这样的平台。但是,怎样将你的更新同步到服务器呢?你可以在每次变更后用 SSH 手动登录服务器, 从 GitHub 上拉取最新代码;你也可以使用 GitHub 的 webhooks 自动拉取,或者你也可以使用外部软件帮你管理和部署代码。本文将像你展示怎么使用自动化运维工具 Ansible 和 GitHub Actions 自动部署。 Ansible 是一款强大的开源自动化运维软件,它是使用 python 编写,简化了安装过程和可以自动化管理远程机器。

一般而言,Ansible 这样工作的:你需要先创建一个Ansible 清单包括你服务器信息,你需要为 Ansible 设置一种登录服务器的方法。理想状态下,你可以在服务器上创建一个带有 SSH 密钥的新用户,仅供 Ansible 登录使用。其他配置通过 YAML 文件进行设置。

你可以使用 Ansible 做很多事情:安装(网页)服务器 ,保持更新,安装软件,自动扩容等等 — 本教程将只关注如何使用 Ansible 自动部署最新版的代码。我们会准备服务器,配置一个可以在每次将代码推送到 production 分支时自动运行 Ansible 和部署代码的 GitHub Action。
准备

为了让 Ansilble 可以连上并在服务器上工作,你需要做两件事:安装必要的软件(python 和 Git) 以及在服务器上为 Ansible 单独创建一个管理用户。可以通过 SSH 连接到服务器运行以下命令:

# 1. Install Python and Git 安装Python和Git
sudo apt install python3 git
# 2. Create a superuser named "ansible" 创建一个名为ansible的超级用户
sudo useradd -m -G sudo ansible
sudo passwd ansible

现在你的服务器上有了一个 ansible 用户,你应该为该账户创建一个 SSH 密钥对,以替代密码登录,使得连接更加安全。同时要确保为这个新创建的 ansible 用户启用 SSH 密钥,而不是为默认 root 用户启用:

如果你有多个服务器,需要在每个服务器上都重复此步骤(理论上,在所有服务器上都让 Ansible 用户使用同一个的 SSH 公钥)。

Set up the Inventory

所谓的 Ansible Inventory 拥有你服务器的所有信息及所有可能的额外变量。你可以将内容放在一个文件里;也可以放在 GitHub 私有库中,然后在 GitHub Action 中获取。建议使用第二种方式, 因为 inventory 中的一些内容(如服务器 ip) 可能是机密,不应该在你的 git 版本控制中出现(特别是当你的服务器放在Couldflare 防火墙后诸如此类)。

Inventory 被分成服务器分组,相当直接了当:

---
all:
  vars:
    # here you can put any global variables you might need in Ansible playbooks
webserver: # the name of our first server group, you can rename this group or add more groups as you like
  hosts:
    254.254.254.254: # replace this with the IP address of your server. If you have multiple servers, just add them in the following lines

创建剧本(Playbook)

要让 Ansible 知道部署代码的时候怎么该做,我们需要先创建所谓的 Ansible 剧本(playbook)。 它是一个 YAML 文件,将任务分割成不同的步骤。建议在你的 PHP 代码库中为你的 Ansible 剧本创建新的目录。

同时,你也可以为 Ansible Playbooks 单独新建一个代码库,当你的 PHP 代码库中推送到你的主分支时,用以触发在你的 Ansible 库中的 GitHub Action 的执行。这样做的好处是,你可以分离解耦不同的任务,但会显得比较复杂。因此,我建议先用简单的方式,在你的主代码库中创建目录:

Make sure to configure your webserver to forbid accessing your Ansible folder publicly.

在此目录中,创建一个新的YAML文件deploy-playbook.yml. 在这个剧本中,让Ansible做如下一些事情:

  1. 将服务器上PHP代码库克隆到一个临时文件夹C
  2. 可选:做其他我们部署时可能需要做的额外步骤(比如安装composer包)
  3. 将代码复制到目标目录(通常可能web服务器的DocumentRoot目录)
  4. 更新文件权限 (以便web服务器可以访问)

我们可以在playbook中为每一步添加任务:

 ---
- hosts: webserver
  become: yes
  become_user: root
  vars:
    temp_repo_path: "/tmp_repo" # path to the temporary downloadfolder of the repo
  tasks:
    # Get the latest code from your GitHub code repository
    - name: Get latest Application Codebase
      git:
        repo: https://github.com/AnTheMaker/Pesto.git # place the HTTPS git URL of your codebase here
        version: main # replace this with the name of your "production" branch (in most cases "main" or "master")
        dest: "{{ temp_repo_path }}"
        single_branch: yes
        accept_hostkey: true
        depth: 1
      # if your git repository is private, you'll need to create a GitHub deploy key (https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys), upload the private key to your server and uncomment the following line:
      # key_file: ~/github_deploy_private_key.key
      register: code_upload # set a variable in Ansible if this task succeeds
#   do any additional tasks you may need to do. E.g. install composer packages: (uncomment/change as you like)
#   - name: Install Composer Packages
#     composer:
#       command: "install"
#       working_dir: "{{ temp_repo_path }}"
#     environment:
#       COMPOSER_NO_INTERACTION: "1"
#     when: code_upload.changed # only run this task when the code has changed
    - name: Replace live Codebase
      delegate_to: "{{ inventory_hostname }}"
      synchronize:
        src: "{{ temp_repo_path }}"
        dest: /var/www/
        recursive: yes
        delete: yes
      when: code_upload.changed # only run this task when the code has changed
    - name: Update Permissions
      file:
        path: /var/www/html
        state: directory
        recurse: yes
        # change the following two lines to the user of your webserver
        owner: www-data
        group: www-data

Set up GitHub Actions

现在我们有了一个Ansible Playbook 引导如何部署代码以及Inventory包含服务器IP。我们只需设置

GitHub Action使之在每次推送代码到production(或者其他自定义分支名)分支的时候自动运行Ansible。

幸运的是,GitHub上已经有了一个现成的GitHub Action, 叫Run Ansible Playbook 。我们只需配置一下就能使之与我们的安装配合工作。

为此,我们需要在代码块中创建一个GitHub Action的YAML文件,文件路径:.github/workflows/main.yml

 name: Deploy
on:
  # Triggers this Action on push or pull request events on the "main" branch and when manually requested from the "Actions" tab
  push:
    branches: [ main ]
  workflow_dispatch:
jobs:
  deploy_code:
    runs-on: ubuntu-latest
    steps:
      - name: Run Ansible playbook
        uses: dawidd6/action-ansible-playbook@v2.5.0
        with:
          playbook: ansible/deploy-playbook.yml # path to your Ansible playbook
          directory: ./
          key: ${{ secrets.ansible_ssh_private_key }} # the ssh private key for ansible to use to connect to the servers, stored as "ansible_ssh_private_key" in the GitHub secrets
          inventory: ${{ secrets.ansible_inventory }} # the ansible inventory to use, stored as "ansible_inventory" in the GitHub secrets

保存这个工作流文件,将其提交发布到GitHub将会自动启动此Action。以后,当你推送更新到production(或者从你的代码库Action标签页中手动请求部署)时,会执行该Action,运行Ansible, 连接到你的服务器,部署新代码。当运行这个action时,你可以点击Action标签页,查看当前运行的Action(带有所有Ansible运行时可能的错误和警告实时日志)。