Ruby: 高级用法

深入了解 Ruby 部署

👋 欢迎来到 Stackhero 文档!

Stackhero 提供了一种即用型 Ruby 云 解决方案,具有众多优势,包括:

  • 通过简单的 git push 在几秒钟内 部署您的应用程序。
  • 使用您自己的域名,并享受 HTTPS 证书的自动配置以增强安全性。
  • 享受自动备份一键更新以及简单、透明和可预测的定价带来的安心。
  • 通过私有和专用的 VM获得最佳的性能和强大的安全性

节省时间简化您的生活:尝试 Stackhero 的 Ruby 云托管 解决方案只需 5 分钟

到目前为止,我们通过使用以下命令推送 main 分支来部署我们的 Ruby 应用程序:

git push stackhero main

如果您希望部署其他分支,可以使用此命令。将 <BRANCH> 替换为您要部署的分支名称:

git push stackhero <BRANCH>:main

例如,要部署名为 production 的分支,请运行:

git push stackhero production:main

在某些情况下,您可能希望部署标签而不是分支。为此,请运行以下命令。将 <TAG> 替换为您要部署的标签:

git push stackhero '<TAG>^{}:main'

例如,要部署标签 v1.0.0,请运行:

git push stackhero 'v1.0.0^{}:main'

^{} 语法用于引用标签指向的提交。

除了分支或标签,您还可以部署特定的提交。在下面的命令中将 <COMMIT_HASH> 替换为所需提交的哈希:

git push -f stackhero <COMMIT_HASH>:main

例如,要部署哈希为 abcde 的提交,请运行:

git push -f stackhero abcde:main

如果您的生产部署未按预期工作,您可以通过部署旧的提交来回滚。首先,使用以下命令查看您的提交历史:

git log

此命令显示您的存储库中每个提交的日期、提交哈希和描述。例如,您可能会看到如下输出:

commit cccc8b3ebdccb9abc1926ef49ee589dae5c5fe06 (HEAD -> main, stackhero/main)
Author: Developer
Date:   Fri Apr 28 09:36:18 +0000

    Break the code

commit bbbb622301772072c3d82f3cc0d91e29e6e84901
Author: Developer
Date:   Wed Apr 26 12:49:28 +0000

    Update the code

commit aaaa1d8b06535b413e0df8298ccf52339dfef3ff
Author: Developer
Date:   Wed Apr 26 12:44:50 +0000

    Improve the code

如果带有消息 "Break the code"(哈希 cccc...)的提交正在生产中运行,并且您决定回滚到上一个提交 "Update the code"(哈希 bbbb...),请运行:

git push -f stackhero bbbb622301772072c3d82f3cc0d91e29e6e84901:main

为了防止部署损坏的代码并提高生产的稳定性,强烈建议拥有一个 "staging" 环境。

"staging" 环境位于 "development" 环境和 "production" 环境之间,提供生产环境的近乎精确的副本。这使您可以测试代码并确保其质量,然后再将其部署到生产环境。

通过使用 staging 环境,您可以对代码的功能和性能更有信心,确保更可靠和稳健的生产部署。

这种类型的环境将在文档后面讨论。

staging 环境是与 developmentproduction 环境一起使用的最佳实践。它复制您的生产环境,以便您可以在更新和更改上线之前进行测试。

staging 环境必须紧密反映生产环境。

但是,请确保 staging 环境使用生产数据库的克隆,而不是实际的生产数据库。

如果您的 Ruby 服务链接到数据库或其他服务,请在新的 <Project> - Staging 堆栈中重新创建它们。

要在 Stackhero 上设置 staging 环境,请按照以下步骤操作:

  1. 在 Stackhero 仪表板上,将现有堆栈从 <Project> 重命名为 <Project> - Production。例如,如果您的项目名为 Chat Bot,请将堆栈重命名为 Chat Bot - Production
  2. 创建一个名为 <Project> - Staging 的新堆栈。使用前面的示例,这将是 Chat Bot - Staging
  3. 在 staging 堆栈中启动一个 Ruby 服务。
  4. 获取 git remote 命令的值,并按照 Deploying to staging environment 部分中的说明进行操作。

按照这些步骤,您将获得一个正确配置的 staging 环境,以便在更新到达生产之前进行测试和验证。

强烈建议管理诸如 stagingproduction 之类的独立环境。如 Setting up a staging environment 中所述,您可以使用不同的 Git remotes 部署到每个环境。

首先重命名当前的远程存储库。例如,使用以下命令将远程 "stackhero" 重命名为 "stackhero-production":

git remote rename stackhero stackhero-production

接下来,为 staging 环境创建一个新的 Ruby 服务。使用提供的 "git remote add" 命令并按如下方式修改(将 <XXXXXX> 替换为您的服务域):

  • 原始命令:

    git remote add stackhero ssh://stackhero@<XXXXXX>.stackhero-network.com:222/project.git
    
  • 修改后的命令:

    git remote add stackhero-staging ssh://stackhero@<XXXXXX>.stackhero-network.com:222/project.git
    

您现在可以使用以下命令部署到 staging:

git push stackhero-staging main

或使用以下命令部署到生产:

git push stackhero-production main

为了进一步简化部署过程,请考虑使用 improved Makefile version

使用这个改进的 Makefile,可以通过 make deploy-productionmake deploy-staging 轻松地进行生产或 staging 部署。

以下是一个改进的 Makefile,它支持常见任务的多种规则:

  • make dev(或简单地 make):以开发模式启动应用程序。
  • make deploy:将应用程序部署到名为 stackhero 的远程(理想情况下,当您只有一个 Stackhero 实例时)。
  • make deploy-production:将应用程序部署到名为 stackhero-production 的远程。
  • make deploy-staging:将应用程序部署到名为 stackhero-staging 的远程。

Makefile 旨在处理代码已部署的情况,避免 "Everything up-to-date" 错误。

将以下内容复制并粘贴到您的新 Makefile 中:

# 默认执行规则,当调用 "make" 时不带参数
.DEFAULT_GOAL := dev


# Stackhero for Ruby 将在您的实例上执行 "run" 规则。
# 这是在生产和 staging 平台上运行的命令。
run:
  rake assets:precompile
  rake db:migrate RAILS_ENV=production
  RAILS_ENV=production bundle exec puma -C config/puma.rb


# 在开发环境中运行的命令
dev:
  RAILS_ENV=development rails server -b 0.0.0.0


# "deploy" 规则部署到 "stackhero" 实例。
# 适用于只有一个实例的情况。
deploy:
  @$(MAKE) -s deploy-script DEPLOY_REMOTE=stackhero DEPLOY_BRANCH=main


# "deploy-*" 规则部署到 "stackhero-*" 实例。
# 例如,运行 "make deploy-production" 部署到 "stackhero-production",
# 或 "make deploy-staging" 部署到 "stackhero-staging"。
deploy-%:
  @$(MAKE) -s deploy-script DEPLOY_REMOTE=stackhero-$* DEPLOY_BRANCH=main


# 内部部署规则。请勿修改。
deploy-script:
  @echo "正在将分支 \"${DEPLOY_BRANCH}\" 部署到 \"${DEPLOY_REMOTE}\"..."
  @echo

  @if [ -n "$$(git status --porcelain)" ]; then \
    echo "无法部署,因为有未提交的更改:"; \
    echo "\e[0m"; \
    git status -s; \
    echo ""; \
    echo "\e[0;31m"; \
    echo "您可以使用此命令提交更改:"; \
    echo "git add -A . && git commit -m \"Your message\""; \
    echo "\e[0m"; \
    exit 1; \
  fi

  @git push --dry-run ${DEPLOY_REMOTE} ${DEPLOY_BRANCH} 2>&1 | grep -q -F "Everything up-to-date"; \
  EXIT_CODE=$$?; \
  if [ $$EXIT_CODE -eq 0 ]; then \
    echo -n "没有新内容可部署... 强制部署(这将创建一个新提交)? (y/N) "; \
    read answer && \
    case $$answer in \
      y|Y|yes|YES) \
      git commit --allow-empty -m "Force update for deploy purpose to \"${DEPLOY_REMOTE}\"" ; \
      ;; \
      *) \
      echo "没有内容可部署!"; \
      exit 1; \
      ;; \
    esac \
  fi

  git push ${DEPLOY_REMOTE} ${DEPLOY_BRANCH}

在某些时候,您需要管理数据库和第三方服务的令牌或密码等机密信息。安全存储这些机密信息至关重要。避免将机密信息直接嵌入到您的存储库或代码中,因为这会带来严重的安全风险。

环境变量提供了两个显著的好处:

  1. 您的机密信息不会存储在 Git 存储库中,从而降低了如果有人访问您的源代码的风险。
  2. 您可以为不同的环境使用不同的凭据。例如,在生产中连接到您的生产数据库,而在开发中使用开发数据库。

对于开发,在项目的根目录下创建一个 .env 文件。此文件将被 Git 排除,因此永远不会被提交。使用 dotenv gem 自动加载 .env 文件。

首先,将 dotenv-rails gem 添加到您的 Gemfile 中:

# Gemfile
gem 'dotenv-rails', groups: [:development, :test]

然后安装 gem:

bundle install

接下来,在项目的根目录下创建一个 .env 文件并添加您的变量:

RAILS_ENV="development"
DATABASE_PASSWORD="secretPassword"
THIRD_API_PRIVATE_KEY="secretKey"
# ...

最后,确保 .env 文件被 Git 忽略:

echo '.env*' >> .gitignore

对于 staging 和生产,.env 文件既不安全也不实用,因为它不能存储在 Git 存储库中。相反,Stackhero 提供了一种安全的解决方案,可以直接在您的 Ruby 服务配置中管理环境变量。

您可以通过选择您的 Ruby 服务并点击 "Configure" 按钮,在 Stackhero 仪表板中设置这些变量。

在 Ruby 中,您可以使用 ENV 轻松访问环境变量。例如,要检索 DATABASE_PASSWORD,请使用:

ENV['DATABASE_PASSWORD'] # => 'secretPassword'

以下是使用环境变量连接到 RabbitMQ 服务器的示例:

require 'bunny'

class RabbitMQClient
  def initialize
    @connection = Bunny.new(hostname: ENV['RABBITMQ_HOST'],
                            username: ENV['RABBITMQ_USERNAME'],
                            password: ENV['RABBITMQ_PASSWORD'])
    @connection.start
  end

  def publish(queue_name, message)
    channel = @connection.create_channel
    queue = channel.queue(queue_name)
    channel.default_exchange.publish(message, routing_key: queue.name)
  end

  def close
    @connection.close
  end
end

在开发平台上,您的 .env 文件可能包括:

RABBITMQ_HOST='127.0.0.1'
RABBITMQ_USERNAME='developmentUser'
RABBITMQ_PASSWORD='developmentPassword'

对于生产和 staging,请在 Stackhero 仪表板的 Ruby 服务配置下定义您的环境变量,如下所示:

RABBITMQ_HOST='<XXXXXX>.stackhero-network.com'
RABBITMQ_USERNAME='production'
RABBITMQ_PASSWORD='secretProductionPassword'

Ruby 应用程序通常在端口 80(HTTP)和 443(HTTPS)上使用 HTTP 协议。如果您的应用程序需要额外的端口或不同的协议(TCP 或 UDP),请通过 Stackhero 仪表板在您的 Ruby 服务中配置 "Ports Redirections" 设置。

您需要指定入口端口(公开开放)、目标端口(在您的 Ruby 服务中开放)和协议(TCP 或 UDP)。

对于存储用户照片或文档等文件,强烈建议使用对象存储解决方案。对象存储允许您在多个服务和实例之间共享文件,并将存储层与代码分离。这被认为是一种最佳实践。

我们推荐 MinIO 作为一种简单、快速且强大的解决方案,兼容 Amazon S3 协议。

如果您选择本地文件存储,可以使用 Ruby 实例提供的持久存储。此本地存储位于 /persistent/storage/ 目录下。

然而,本地文件存储通常不被推荐,因为它可能不是长期可扩展性和可靠性的最佳实践。

警告:切勿将数据存储在 /persistent/storage/ 文件夹之外!

将数据存储在持久存储文件夹之外的任何位置可能会导致在实例重启、更新或推送新代码时数据丢失。

如果您使用 macOS,您可能会发现每次推送代码时输入 SSH 私钥密码很不方便。虽然安全性至关重要,但您可以通过将密码安全地存储在 Apple 的钥匙串中来提高便利性。

可能会有删除 SSH 私钥密码的诱惑,但这并不建议。

相反,使用以下命令将您的密钥密码存储在钥匙串中,适用于名为 id_ed25519 的密钥:

ssh-add --apple-use-keychain ~/.ssh/id_ed25519

运行此命令后,您不应再被提示输入密钥密码。如果您使用的是 RSA 密钥,请将 id_ed25519 替换为 id_rsa,如下所示:

ssh-add --apple-use-keychain ~/.ssh/id_rsa