Octopus-博客中文翻译-三-

Octopus 博客中文翻译(三)

原文:Octopus Blog

协议:CC BY-NC-SA 4.0

采访:部署 NuGet.org-章鱼部署

原文:https://octopus.com/blog/deploying-nuget.org

支持 NuGet 包管理器的网络画廊和基础设施 NuGet 已经成为。网络开发。很难想象没有 NuGet 的时代,那时使用开源库意味着搜索网页、下载 ZIP 文件和手动添加参考资料。NuGet 也是 Octopus Deploy 所依赖的基础,因为我们使用它作为我们的应用程序打包格式。

使用 NuGet 支持的部署工具来部署 NuGet 背后的服务是一件非常酷的事情!来自微软的安德鲁·斯坦顿护士非常友好地带我参观了 NuGet 团队如何使用 Octopus Deploy,这是我们在这个 22 分钟的视频中记录的。

NuGet.org 运行在 Windows Azure 之上。他们有三种不同的云服务,并且需要开发、测试和生产环境,总共需要 9 个云服务终端。当你有一两个服务时,从 Visual Studio 发布 Azure 包是可行的,但一旦你管理 9 个服务,就没什么意思了。观看视频,了解他们如何使用 Octopus Deploy 来自动化其 Azure 部署!

用 Octopus 部署 PHP 应用程序

原文:https://octopus.com/blog/deploying-php

PHP 是基于网络的应用程序中最流行的语言。这种流行导致了广泛的产品,这些产品将部署您的 PHP 代码,但不一定是您的整个堆栈。应用程序通常包括 web 前端以外的组件,如数据库、API,甚至容器化的微服务。

在这篇文章中,我演示了如何将 PHP 应用程序部署到使用 MySQL 作为数据库后端的 NGINX web 服务器上。

示例应用程序

我为这个帖子选择的示例应用程序是租车项目

只需对单个文件稍加修改,这个应用程序就可以开箱即用,非常适合这个演示。源代码包含一个 MySQL 数据库脚本,该脚本将创建表模式并用数据播种数据库。我将在这篇文章的后面讨论需要做的修改。下面是修改项目的链接。

构建您的 PHP 应用程序

PHP 是一种脚本语言,这意味着它不需要编译就可以部署。然而,在 PHP 应用程序中使用构建服务器有一些好处:

  • 如果您的 PHP 应用程序使用 Composer 作为依赖项管理器,您可以将它包含在应用程序的构建中,以便在构建运行时收集最新版本的依赖项。
  • 使用构建服务器 Octopus Deploy 插件来简化集成,例如:
    • 打包应用程序
    • 将包推送到 Octopus Deploy 服务器或第三方包解决方案(Nexus、Artifactory 等)。)
    • 将构建信息推送到 Octopus 部署
    • 创建版本
    • 部署和/或推广版本

对于这篇文章,我选择 Jenkins 作为我的构建服务器,有三个步骤:

  • 打包 web 前端
  • 打包数据库脚本
  • 将包推送到 Octopus 部署

打包 web 前端

首先,让我们看一下我为这个项目所做的修改。如前所述,汽车租赁应用程序使用 MySQL 作为其数据库后端。数据库连接信息位于src/includes/config.php中。使用 Octostache 和模板中的替代变量功能,我们可以参数化连接信息:

<?php 
// DB credentials.
define('DB_HOST','#{MySQL.Server.Name}:#{MySQL.Server.Port}');
define('DB_USER','#{MySQL.Admin.User.Name}');
define('DB_PASS','#{MySQL.Admin.User.Password}');
define('DB_NAME','#{Project.Database.Name}');
// Establish database connection.
try
{
$dbh = new PDO("mysql:host=".DB_HOST.";dbname=".DB_NAME,DB_USER, DB_PASS,array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'",PDO::MYSQL_ATTR_SSL_CA => '/var/www/html/DigiCertGlobalRootG2.crt.pem',PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false));
}
catch (PDOException $e)
{
exit("Error: " . $e->getMessage());
}
?> 

对于这个演示,我使用 Azure MySQL PaaS,它需要一个到数据库的 SSL 连接。我需要将以下内容添加到 PDO 期权组件数组中(如上所示):

PDO::MYSQL_ATTR_SSL_CA => '/var/www/html/DigiCertGlobalRootG2.crt.pem'
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false 

使用 Octopus Deploy Jenkins 插件,打包应用程序进行部署很容易。只需选择Octopus Deploy:Package application步骤并填写以下内容:

  • Octopus Deploy CLI:选择在Global Tool Configuration中配置的 Octopus Deploy CLI
  • 包 ID:包的名称,即CarRental.PHP.Web
  • 版本号:包的版本号
  • 包格式:Zip 或 Nuget
  • 包基础文件夹:对于我的回购,它是${WORKSPACE}/src
  • 包输出文件夹:${WORKSPACE}

打包数据库脚本

源代码中包含一个脚本,该脚本创建模式并用数据填充数据库。数据库脚本文件是专门为使用 Flyway 数据库迁移产品而命名的:

  • Octopus Deploy CLI:选择Global Tool Configuration中配置的 Octopus Deploy CLI
  • 包 ID:包的名称,即CarRental.PHP.Db
  • 版本号:包的版本号
  • 包格式:Zip 或 Nuget
  • 包基础文件夹:对于我的回购,它是${WORKSPACE}
  • 包输出文件夹:${WORKSPACE}
  • 包包含路径:**/sql/*.*

将包推送到 Octopus 部署

使用Octopus Deploy:Push packages插件步骤,您可以在一个步骤中将 web 和数据库包推送到 Octopus Deploy:

  • Octopus Deploy CLI:选择在Global Tool Configuration中配置的 Octopus Deploy CLI
  • Octopus Deploy Server:选择要推送的 Octopus Deploy server(在 管理詹金斯➜配置系统 中定义)
  • 空间:选择要推进到的空间(如果留空,则使用默认值)
  • 包路径:/.zip

我们的 PHP 应用程序现在已经打包好,可以部署了。

部署 PHP 应用程序

包准备好了,我们就可以定义我们的部署过程了。

这篇文章假设您已经熟悉创建 Octopus Deploy 项目,所以我不会涉及这一部分。如果你不熟悉这个话题,看看我们的入门指南。

我们的部署流程将包括以下步骤:

  • 如果 MySQL 数据库不存在,则创建它
  • Flyway 数据库迁移(使用执行容器)
  • 部署到 NGINX

创建 MySQL 数据库

如果数据库还不存在,这一步将在 MySQL 服务器上创建一个数据库。这一步只需要填写几个输入:

  • 服务器:MySQL 服务器的名称或 IP 地址
  • 用户名:有足够权限创建数据库的用户名
  • 密码:用户帐户的密码
  • 数据库名称:要创建的数据库的名称
  • 端口:MySQL 监听的端口(默认为 3306)
  • 使用 SSL:连接到 MySQL 时是否使用 SSL(这对于我来说是必须的,因为我使用的是 Azure MySQL PaaS)

Flyway 数据库迁移

在这篇文章中,我使用了新创建的 Flyway 模板,它可以与执行容器一起使用。

  • 执行位置:在工作线程上运行一次
  • 工人池:包含安装了 Docker 的工人的池
  • 容器图像:octopuslabs/flyway-workertools:latest(您必须配置外部 feed 才能使用 Docker Hub)

该步骤需要以下信息:

  • 飞行路线包:CarRental.PHP.Db
  • 飞航命令:Migrate
  • -Url: JDBC 连接 Url,即 jdbc:mysql:// <ServerName> : <Port> / <DatabaseName> }?server time zone = UTC&use SSL = true
  • -用户:可以更新数据库的用户
  • -Password:用户帐户的密码

将汽车租赁部署到 NGINX

第三步也是最后一步是将汽车租赁 PHP 应用程序部署到 NGINX web 服务器上。选择 NGINX 内置步骤模板,向流程添加一个步骤:

点击配置功能并启用:

  • 定制部署脚本
  • 替换模板中的变量

包装详情

封装细节部分,选择CarRental.PHP.Web封装。

自定义部署脚本

在部署后脚本窗口中添加以下内容:

nginx -s reload 

确保为脚本选择适当的语言。我选择 Bash,因为我要在 Linux 上部署 NGINX。

【T2

模板中的替代变量

指定包含数据库连接信息的config.php文件的位置,以便用目标文件输入:includes/config.php中的适当值进行更新

NGINX Web 服务器

本节将定义 NGINX 步骤的设置。在这篇文章中,我填写了绑定和位置。

粘合剂

对于这个示例应用程序,我只需要一个绑定:

  • 协议:http
  • 端口:8088(或者您希望的任何端口)
  • IP 地址:*
位置

要配置 NGINX 来运行我们的 PHP 应用程序,我们需要定义三个位置:

Location: = /
Directives:

- index = `index.html`

Location: /
Directives:

- root = #{Octopus.Action.Package.InstallationDirectoryPath}/
- try_files = $uri /index.php$is_args$args

Location: ~ [^/]\.php(/|$)
Directives:

- fastcgi_split_path_info = ^(.+?\.php)(/.*)$
- fastcgi_pass = unix:/run/php/php7.2-fpm.sock
- fastcgi_index = index.php
- include = fastcgi.conf
- root = #{Octopus.Action.Package.InstallationDirectoryPath}/
- fastcgi_param = ENVIRONMENT_NAME #{Octopus.Environment.Name} 

我们现在已经配置了将 PHP 应用程序部署到 NGINX 的步骤。剩下的工作就是创建一个版本并进行部署。

部署

部署您的版本后,您应该会收到类似于以下内容的输出:

您可能会注意到 NGINX 步骤显示警告,但是,这是正常的。NGINX 将信息消息写入 stderr 流,Octopus 将其解释为可能的错误,并标记为警告。

访问我们的服务器,我们可以看到我们的 PHP 应用程序已经启动并运行。

结论

世界上大多数人都在 PHP 上运行他们的应用程序。在这篇文章中,我演示了如何使用 Octopus Deploy 通过数据库后端轻松部署 PHP 应用程序。

愉快的部署!

部署 Ruby web 应用程序——Octopus Deploy

原文:https://octopus.com/blog/deploying-ruby

Twitter、Airbnb、Shopify 和 GitHub 是科技行业的知名企业。除了一眼就能认出来,它们还有一些共同之处;都是用红宝石写的。

在这篇文章中,我演示了如何使用 Octopus Deploy 部署用 Ruby 编写的 web 应用程序,包括数据库迁移。

示例应用程序

在这篇文章中,我使用的是 Veggie Tracker 示例应用程序。

此示例包括用于创建数据库和执行数据库迁移的 web 应用程序和代码。通过对项目的一些修改,我让这个应用程序在我的本地环境中运行得相当快(修改后的版本见 GitHub )。

Ruby 应用服务器和 Web 服务器

对于 Ruby 来说,理解应用服务器Web 服务器之间的区别很重要。

应用服务器运行 Ruby 应用程序,并且通常在没有 Web 服务器的情况下工作。这种方法的缺点是应用服务器只能服务于它正在运行的应用程序,而不能像 Web 服务器那样处理多个应用程序。

此外,应用服务器通常不处理请求压缩或 SSL/TLS 之类的事情。出于这个原因,Ruby web 应用程序最典型的配置是在应用程序服务器前面有一个类似 Apache 或 NGINX 的 web 服务器。

选择应用服务器

当用 Ruby 语言开发时,你有许多应用服务器可供选择,例如 Unicorn、Thin、Puma、Passenger 等。

Veggie Tracker 应用程序的原始应用服务器叫做 Shotgun 。该项目的自述文件指出,您只需从命令行运行Shotgun就可以让应用程序在您的本地机器上运行。如果您在 Linux 上开发,这是可行的。

虽然我的服务器是 Linux,但我的开发机器是 Windows,所以我需要既能用于 Windows 又能用于 Linux 的东西。Puma 应用服务器兼容 Linux 和 Windows。切换到美洲狮就像从 Gemfile 中移除散弹枪并添加Puma一样简单。

配置 Puma 用于套接字

在这篇文章中,我希望 Puma 使用套接字而不是服务器上的另一个端口。要配置 Puma 使用套接字,您需要在config子文件夹中创建一个包含以下内容的puma.rb文件:

# Change to match your CPU core count
#workers 2

# Min and Max threads per worker
threads 1, 6

app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/shared"

# Default to production
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env

# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"

# Logging
stdout_redirect "#{shared_dir}/logs/puma.stdout.log", "#{shared_dir}/logs/puma.stderr.log", true

# Set master PID and state locations
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
activate_control_app

on_worker_boot do
  require "active_record"
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env])
end 

上面的代码被配置为与它将要被部署到的 Linux NGINX 服务器一起工作。请删除此文件,以便在 Windows 上本地运行它。

配置 database.yml

最初的应用程序被配置为使用 SqlLite 作为数据库,然而,它可以很容易地被修改为使用类似于 Postgres 的东西。有两个修改来实现这一点:

1。Gemfile中增加对 Postgres gem 的引用:

gem 'pg' 

2。config文件夹中创建一个database.yml文件:

# If you want to change this file, please keep the changes in your working
# copy by using
#
#     git update-index --skip-worktree config/database.yml
#
# or just use DATABASE_URL, in which case Rails will happily skip the whole
# file.
#
# See https://github.com/coopdevs/timeoverflow/wiki/Keeping-your-local-files
# for more information
#

defaults: &defaults
  adapter: postgresql
  username: 'postgres'  # default is null
  database: 'veggietracker'
  password: 'mypassword'
  host: 'my posgresl server'
  port: 5432

development:
  <<: *defaults

test:
  <<: *defaults

staging:
  <<: *defaults

production:
  <<: *defaults 

database.yml文件允许您根据定义的环境覆盖连接属性。然而,在这篇文章中,我配置了database.yml文件来继承defaults的所有内容,因为我将在 Octopus Deploy 中使用结构化配置变量特性。

构建 Ruby 应用程序

由于 Ruby 是一种脚本语言,所以不需要构建应用程序。然而,在 Ruby 应用程序中使用构建服务器有明显的优势:

  • 为应用程序执行收集所有相关的 gem
  • 使用构建服务器 Octopus Deploy 插件或集成以便于集成,例如:
    • 打包应用程序
    • 将包推送到 Octopus Deploy 服务器或第三方包解决方案(Nexus、Artifactory 等)。)
    • 将构建信息推送到 Octopus 部署
    • 创建版本
    • 部署和/或推广版本

在这篇文章中,我使用 GitHub Actions 作为构建服务器。下面是执行以下操作的 YAML:

  • 配置 GitHub 操作以使用 Ruby
  • 设置软件包版本号
  • 安装依赖 gem,将 gem 放在供应商子文件夹中——这允许应用程序包含它需要的所有依赖项,而不需要直接在服务器上安装 gem
  • 配置 GitHub 操作以使用 Octopus CLI
  • 创建工件文件夹
  • 打包 VeggieTracker 应用程序并将归档文件放在 artifacts 文件夹中
  • 将 VeggieTracker 工件推送到 Octopus 部署服务器
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run. 
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6

      # Set the build version number
      - name: Set Version
        run: echo "PACKAGE_VERSION=$(date +'%Y.%m.%d').$GITHUB_RUN_NUMBER" >> $GITHUB_ENV

      - name: Install dependencies
        run: |
          # Set the default gem installation path to a directory alongside the Ruby application code.
          # This allows the dependencies to be packaged with the application.
          export WORKSPACE="$(pwd)"
          export GEM_HOME="$WORKSPACE/vendor"
          export GEM_PATH="$WORKSPACE/vendor"
          export PATH="$PATH:$WORKSPACE/vendor/bin"

          # Install the specific version of Bundler defined in the Gemfile.lock file
          gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)"

          # Use bundler to install the other dependencies
          bundle install

      # Install Octopus action
      - name: Install Octopus CLI
        uses: OctopusDeploy/install-octopus-cli-action@v1.1.1
        with:
          version: latest

      # Create artifacts folder
      - name: Create artifacts folder
        run: mkdir "$GITHUB_WORKSPACE/artifacts"

      # Package VeggieTracker.Web
      - name: Package Flyway
        run: |
          octo pack --id="VeggieTracker.Web" --format="Zip" --version="$PACKAGE_VERSION" --basePath="$GITHUB_WORKSPACE" --outFolder="$GITHUB_WORKSPACE/artifacts"

      # Push packages to octopus deploy
      - name: Push packages to Octopus
        run: |
          octo push --package="$GITHUB_WORKSPACE/artifacts/VeggieTracker.Web.$PACKAGE_VERSION.zip" --server="${{ secrets.OCTOPUS_SERVER_URL }}" --apiKey="${{ secrets.OCTOPUS_API_KEY }}" --space="${{ secrets.OCTOPUS_SERVER_SPACE }}" 

构建完成后,我们可以专注于在 Octopus Deploy 中创建部署。

使用 Octopus Deploy 部署 VeggieTracker

这篇文章假设你熟悉用 Octopus Deploy 创建项目。

Veggie Tracker 应用程序的部署过程由一个包含多个组件的步骤组成,我将在后面介绍这些组件。

变量

在定义我们的流程之前,让我们创建要在部署中使用的变量:

  • Project.NGINX.Port -端口 NGINX 将监听。
  • defaults:database-veggie tracker 应用程序的数据库名称。
  • defaults:host-PostgreSQL 服务器的主机名或 IP 地址。
  • defaults:password-PostgreSQL 用户帐户的密码。
  • defaults:port-PostgreSQL 服务器监听的端口号。
  • defaults:username-PostgreSQL 服务器上帐户的用户名。

defaults:开头的变量是与结构化配置变量功能一起使用的变量。

过程

添加一个部署到 NGINX 的步骤。

点击配置功能按钮,启用自定义部署脚本结构化配置变量功能。

包装详情

包裹详情部分,选择蔬菜跟踪者。Web 包。

自定义部署脚本

作为部署过程的一部分,我们将运行两个脚本。

部署脚本

Ruby 应用程序数据库更新的一种流行方法是在代码中定义数据库更新。Veggie Tracker 应用程序包括一个名为 db 的文件夹,它使用代码定义数据库结构。

为了执行数据库创建和迁移,将下面的代码放在部署脚本窗口中。确保选择 Bash 作为要使用的语言。

# Ensure the bin files are executable
sudo chmod +x -R "#{Octopus.Action[Deploy to Nginx].Output.Package.InstallationDirectoryPath}/vendor/bin"

# Set variables
ROOTDIR=#{Octopus.Action[Deploy to Nginx].Output.Package.InstallationDirectoryPath | Replace "%" "%%"}
export GEM_HOME="${ROOTDIR}/vendor"
export GEM_PATH="${ROOTDIR}/vendor"

# Install platform specific gems
gem install bcrypt
gem install bond
gem install nio4r
gem install pg
gem install puma

# Run database migrations
${ROOTDIR}/vendor/bin/rake db:create
${ROOTDIR}/vendor/bin/rake db:migrate 

这个脚本安装了一些 Ruby Gems。这是必需的,因为 GitHub Actions 使用基于 Ubuntu 的容器来执行构建。这个平台被标识为x86_64-linux,而 Ubuntu VM 被标识为x86_64-linux-gnu。虽然很相似,但编译器生成了不同的不兼容的二进制文件。

受此影响的宝石在构建日志中用Installing X with native extensions标识出来,这些宝石需要在目标平台上重新构建。

部署后脚本

NGINX 步骤配置 NGINX 来服务 Puma Ruby 应用服务器,但是我们需要一些东西来启动 Puma,尤其是在重启之后。

以下脚本将 Puma 应用服务器配置为作为 Linux 服务启动:

SYSTEMD_CONF=/etc/systemd/system
SERVICE_USER=$(whoami)
ENVIRONMENT=#{Octopus.Environment.Name}

# This is used to generate the systemd filename, so we remove any chars that might be problematic for filenames
APPNAME=#{Octopus.Action[Deploy to Nginx].Package.PackageId | Replace "[^a-zA-Z0-9]" -}-#{Octopus.Environment.Name}
# This path is referenced by the systemd service in multiple places, and systemd treats the % char as special,
# so it is escaped with a second % char
ROOTDIR=#{Octopus.Action[Deploy to Nginx].Output.Package.InstallationDirectoryPath | Replace "%" "%%"}
SYSTEMD_SERVICE_FILE=${SYSTEMD_CONF}/${APPNAME}.service

# Application systemd service configuration
echo "Creating ${APPNAME} systemd service configuration"
cat > "${APPNAME}.service" <<-EOF
[Unit]
Description=${APPNAME} service
After=network.target

[Service]
# Expose the gems that were bundled up with the application package
Environment="GEM_PATH=${ROOTDIR}/vendor"
Environment="GEM_HOME=${ROOTDIR}/vendor"
Environment="PATH=${PATH}:${ROOTDIR}/vendor/bin"
WorkingDirectory=${ROOTDIR}
User=${SERVICE_USER}
Group=${SERVICE_USER}
#ExecStart=${ROOTDIR}/vendor/bin/puma -C ${ROOTDIR}/config/puma.rb #-e ${ENVIRONMENT}
ExecStart=${ROOTDIR}/vendor/bin/puma
Restart=always
RestartSec=10
SyslogIdentifier=${APPNAME}
[Install]
WantedBy=multi-user.target
EOF
sudo mv "${APPNAME}.service" ${SYSTEMD_CONF}/${APPNAME}.service

# Ensure the bin files are executable
chmod +x -R "#{Octopus.Action[Deploy to Nginx].Output.Package.InstallationDirectoryPath}/vendor/bin"

# Any changes to a system file are picked up by reloading the systemd daemon
sudo systemctl daemon-reload
# Enable the service so it starts on boot
sudo systemctl enable "${APPNAME}.service"
# Start or restart the service to pick up any changes
sudo systemctl restart "${APPNAME}.service" 

结构化配置变量

我们将使用结构化配置变量特性来替换database.yml中的值,以便连接到数据库服务器。这个文件位于我们应用程序的config文件夹中。

对于目标文件,输入config/database.yml

NGINX Web 服务器

现在我们为 NGINX 定义绑定和位置等项目。

粘合剂

删除缺省绑定,并用项目变量中配置的端口变量替换它。

位置

我们的应用程序需要定义两个位置。这两个位置都有一些也将被应用的指令。点击添加位置开始。

位置/

第一个位置是/,需要两条指令。

点击添加指令按钮,输入以下内容:

位置@应用程序

第二个地点是@app。这个位置需要用一个指令配置为http://unix:#{Octopus.Action.Package.InstallationDirectoryPath}/shared/sockets/puma.sock的反向代理。

  • 指令:proxy_set_header
  • 数值:Host $http_host

位置确定后,我们就完成了部署过程。您可以创建一个版本并进行部署。

Linux 上的一些程序将信息消息写入 stderr 流。Octopus 将这些解释为可能的错误,并以红色显示。看到这样的消息很正常。

打开浏览器,我们可以看到部署的应用程序正在运行。

结论

在这篇文章中,我演示了如何将一个用 Ruby 编写的 web 应用程序部署到 NGINX web 服务器上,包括数据库迁移。

愉快的部署!

部署软件不应该像看牙医一样

原文:https://octopus.com/blog/deploying-software-shouldnt-feel-like-visiting-the-dentist

作为项目经理,部署软件的过程充满了焦虑和恐惧。在我的职业生涯中,我可以无数次地想到,在临近发布截止日期的阴影下,不得不部署我们的应用程序的想法类似于考虑即将去看牙医。事实上,即使是让我们的“令人敬畏的新功能”进入用户手中的机制也感觉像拔牙。

诱惑是尽可能少地部署,就像诱惑是尽可能少地去看牙医。你知道你应该更有规律地去,但是你害怕会很痛。你甚至可能会有一个难以忘怀的记忆,那就是上次去牙钻时,你的珍珠白落在了牙钻的接收端。就我个人而言,光是想想就牙疼。

你可能会发现自己在找借口并推迟去看牙医,而不是定期去看牙医,也许是每六个月一次。结果,你从来没有抽时间去做检查,两年后,当你有了一个大峡谷大小的蛀牙时,你才不得不去做牙科手术。我知道我已经这样做了,充分意识到它可能发生的风险,并随后在它发生时惩罚了自己。具有讽刺意味的是,最终坐在牙医的椅子上的马拉松式的工作导致了难以忘怀的记忆,迫使我从一开始就避免检查。

在软件开发领域,在软件部署之间留出一段较长的时间就像放弃定期的牙齿检查一样危险。部署之间的差距越大,未经客户验证的工作量就越大——代码更改、数据库模式修改、配置变更。部署之间的功能和代码增量越大,新版本就越有可能破坏一些严重的东西,释放出一大群尖锐的错误,这些错误会消耗您的时间,并使您的下一次开发迭代计划支离破碎。

不要担心在两个版本之间你已经建立了一个巨大的变更列表,只要假设你的决定是正确的,你会感觉更好。你可以假设你做了一个很好的决定,关于添加哪些新功能,如何设计用户界面使其“易于使用”,以及当它以淡紫色绘制时,用户是否会更喜欢它。然而,你只能通过把它交给你的用户来证明你得到了正确的东西。版本之间的差距越大,你赌的开发工作越“正确”。当最终发布时,你的用户会通过销售数据、提出的错误、直接反馈甚至社交媒体来告诉你是否做对了。所以,如果你在紫红色上画错了,痛苦才真正开始。

回到牙医的椅子上,两次检查之间的间隔时间越长,你在两次检查之间吃的食物就越多,牙齿上堆积的未被抑制的细菌就越多。你假设你刷牙的方式是正确的,你对使用牙线的沉默没有对你的牙齿卫生造成太大影响。然而,如果你错了,事情就会变得痛苦。

因此,经常部署似乎是明智的,对吗?问题是,这个比喻已经过时了(抱歉),软件开发团队觉得部署比看牙医还难。部署过程可能是一项巨大的开销,尤其是与拨打电话号码并前往当地牙科诊所相比。有代码审查要进行,测试计划要执行,文档要写,许多环境要更新,管理任务要完成。开发团队害怕消耗他们生命中几天时间的部署过程,并且总是提出困难的问题和强迫不舒服的决定,这是可以原谅的。

实际上,部署应该比去看牙医更简单、更容易、更便宜、更快捷。为了达到这种状态,软件开发团队需要做三件事:

  • 自动化他们的部署机制
  • 重新评估并简化他们的管理实践和流程
  • 明确专注于向用户交付有价值的、增量的功能

一次做所有的事情看起来令人生畏,所以软件开发团队能做的最好的事情就是“现在”就开始把事情做得更好。即使他们目前只能自动化他们部署机制的一小部分,或者删减他们发布文档的一小部分,或者决定不测试应用程序中未被充分利用的区域。随着他们逐渐减少部署开销,他们将越来越多地获得常规用户验证和风险缓解带来的好处,这些好处远远超过了时间和精力成本。此外,一旦您开始经常部署,每个人都会开始专注于消除剩余的开销。

自 2008 年年中以来,我们一直在努力在 Redgate 应用敏捷软件开发方法,但直到我们要求我们的开发团队更频繁地部署,我们仍然花了五年时间。在应对这一挑战的过程中,我们犯了一些错误,发布了一些不应该发布的版本。然而,我们进行了逐步的改进,我们消除或解决了部署过程中的瓶颈,我们添加了允许我们发现问题版本的机制。

值得一提的是,我们所做的最重要的改进是让每个团队都能够按下按钮来部署他们的软件。Redgate 优秀的 DevOps 团队提供了一个定制的自动化部署工具,让我们可以做到这一点。这涉及到 Redgate 方面的大量投资,但这是值得的,因为这意味着我的团队可以随时部署我们的产品。实际上,我们只是受限于我们将工作分成离散的、有价值的块的能力,以及我们的用户对软件更新的容忍度。

软件开发团队应该尽可能频繁地进行部署。每个月。每次冲刺。甚至每天,如果你的用户对此满意的话。缩短部署之间的时间间隔可以减少未经用户验证的未经证实的变更数量,并降低单个版本中有过多尖锐错误占用您时间的风险。此外,这意味着部署将不再像坐在牙医的椅子上一样。


这是 Redgate 产品交付主管克里斯·史密斯的客座博文。Redgate 通过加速软件交付和帮助遵守数据保护法规的解决方案,帮助您满足业务和 IT 的需求。了解更多

使用 Octopus Deploy - Octopus Deploy 部署 SQL Server DACPAC

原文:https://octopus.com/blog/deploying-sql-server-dacpac-octopus

数据库管理员(DBA)常常一提到自动化数据库部署就畏缩不前。他们的工作是确保服务器和数据库保持可用和健康,不受他们控制的进程会让他们紧张。引入自动对数据库结构或数据进行大规模修改的过程似乎与他们的职责形成了鲜明的对比。但是,使用带有 Octopus Deploy 的 DACPAC 来自动部署到 SQL Server 可以帮助您完成开发运维之旅。

这篇文章向您展示了如何使用 DACPAC 和 Octopus Deploy 从项目创建到部署自动化数据库更新到 Microsoft SQL Server。

示例项目:Sakila

这篇文章将把 Sakila 数据库部署到一个 Microsoft SQL 服务器上。

Sakila 项目包含表、约束、存储过程、视图和用户定义的函数,以展示 Microsoft DACPAC 技术的全部功能。

Sakila Git repo 包含使用不同部署方法将 Sakila 数据库部署到多种数据库技术的源代码。这篇文章特别关注微软的 DACPAC 版本。

创建数据库项目

SQL Server 数据库项目类型不是 Visual Studio 自带的。要创建它,您必须安装 SQL Server 数据工具 (SSDT)扩展。SSDT 包含数据库项目、SQL Server Reporting Services(SSRS)项目和 SQL Server Integration Services(SSIS)项目的项目类型。

安装完扩展后,创建一个新项目,并选择 SQL Server 类别、SQL Server 数据库项目类型。

右键点击空间,选择导入,然后选择数据库...连接到现有数据库:

配置一个连接,然后点击开始

该过程完成后,您的项目应该如下所示:

创建构建

在创建生成之前,首先确保您的生成代理已配置为生成 SQL Server 数据库项目。除了 SSRS 项目之外,MSBuild 无法生成 SSDT 项目。您的生成代理至少需要满足以下要求:

本节的剩余部分使用 Microsoft Azure DevOps 来配置构建。如果您使用的是不同的构建服务器,只要构建代理安装了上述工具,您就应该能够做同样的事情。

构建将包括以下步骤:

  • 构建 DACPAC 解决方案
  • 打包文件
  • 推送构建信息
  • 将包装推到包装进料口

构建 DACPAC 解决方案

向您的构建管道添加一个 Visual Studio 构建步骤。将 clean 设置为true将确保在进行后续构建时,文件夹中没有任何剩余的工件。

- task: VSBuild@1
  inputs:
    solution: 'dacpac\mssql\sakila.mssql.dacpac.sln'
    msbuildArgs: '/p:OutDir=$(build.stagingdirectory)'
    clean: true 

打包文件

将 Octopus 步骤的包应用程序添加到管道中。这将压缩.dacpac文件,以便 Octopus Deploy 可以部署它。包括。dacpac 文件和发布配置文件 XML 文件。

- task: OctopusPack@4
  inputs:
    PackageId: 'sakila.dacpac'
    PackageFormat: 'Zip'
    PackageVersion: '$(Build.BuildNumber)'
    SourcePath: '$(build.stagingdirectory)'
    OutputPath: '$(Build.ArtifactStagingDirectory)'
    Include: |
      sakila.mssql.dacpac.dacpac
      sakila.mssql.dacpac.publish.xml 

推送构建信息

查看与这个构建相关联的提交和工作项是很有用的。将推送包构建信息添加到 Octopus 部署

- task: OctopusMetadata@4
  inputs:
    OctoConnectedServiceName: 'Local Octopus Deploy'
    Space: 'Spaces-1'
    PackageId: 'sakila.dacpac'
    PackageVersion: '$(Build.BuildNumber)'
    Replace: 'false' 

使用 Azure DevOps assistant,您可以配置此步骤中使用的OctoConnectedServiceName

将包装推到包装进料口

构建过程的最后一步是将包推送到存储库。这篇文章将使用 Octopus Deploy 的内置库,但是也支持其他外部提要类型,包括 Azure DevOps、Artifactory、Nexus 等等。

- task: OctopusPush@4
  inputs:
    OctoConnectedServiceName: 'Local Octopus Deploy'
    Space: 'Spaces-1'
    Package: '$(Build.ArtifactStagingDirectory)\*.zip'
    Replace: 'false' 

完整的 YAML 管道将类似于此:

trigger:
- master

pool: Default

steps:
- task: VSBuild@1
  inputs:
    solution: 'dacpac\mssql\sakila.mssql.dacpac.sln'
    msbuildArgs: '/p:OutDir=$(build.stagingdirectory)'
    clean: true

- task: OctopusMetadata@4
  inputs:
    OctoConnectedServiceName: 'Local Octopus Deploy'
    Space: 'Spaces-1'
    PackageId: 'sakila.dacpac'
    PackageVersion: '$(Build.BuildNumber)'
    Replace: 'false'
- task: OctopusPack@4
  inputs:
    PackageId: 'sakila.dacpac'
    PackageFormat: 'Zip'
    PackageVersion: '$(Build.BuildNumber)'
    SourcePath: '$(build.stagingdirectory)'
    OutputPath: '$(Build.ArtifactStagingDirectory)'
    Include: |
      sakila.mssql.dacpac.dacpac
      sakila.mssql.dacpac.publish.xml
- task: OctopusPush@4
  inputs:
    OctoConnectedServiceName: 'Local Octopus Deploy'
    Space: 'Spaces-1'
    Package: '$(Build.ArtifactStagingDirectory)\*.zip'
    Replace: 'false' 

创建部署流程

这篇文章假设你知道如何创建一个 Octopus 项目,但并不涉及这个主题。

DACPAC 部署流程包括以下步骤:

  • (可选)如果数据库不存在,则创建数据库:有些人喜欢将此活动放在操作手册中,而不是作为部署过程的一部分。
  • 将 DACPAC 部署到 SQL Server。

(可选)如果数据库不存在,则创建该数据库

这一步连接到一个 SQL server 并创建一个数据库(如果它不存在的话)。点击添加步骤,选择 SQL -如果不存在则创建数据库。该步骤可以在工作机上运行。

填写模板字段:

  • SQL Server :服务器名称。
  • SQL 登录 : SQL 认证用户名,如果使用 Active Directory 认证,则留空。
  • SQL 密码:SQL 认证账户的密码,如果使用 Active Directory 认证,则留空。
  • 要创建的数据库:要创建的数据库名称。
  • 命令超时:等待创建数据库命令完成的秒数。
  • Azure 数据库版本:如果你正在使用 Azure SQL,选择要创建的版本。如果留空(并使用 Azure SQL),Azure 将默认为 Standard。

将 DACPAC 部署到 SQL Server

有几个 DACPAC 社区步骤模板可供选择:

  • SQL - Deploy DACPAC :这个版本的模板是在 Octopus Deploy 的 Workers 特性可用之前创建的。该模板必须在目标上执行,并且需要一个部署包步骤,首先将 DACPAC 部署到目标上。
  • SQL-Deploy DAC PAC from Package Parameter:该模板与 Worker 兼容,使用内置的包选择器。此外,该模板可以动态下载 SQL PowerShell 模块(如果选择的话),并且不需要在工作机器上安装任何附加软件。
  • SQL-Deploy DACPAC from Referenced Package:该模板使用两个包,一个包含执行 DACPAC 部署所需的二进制文件,另一个是 DAC PAC 本身。
  • SQL-Deploy DAC PAC with AAD Auth support:这是最新的可用模板,包含使用 Azure Active Directory 认证数据库服务器的能力(这篇博文演示了如何配置步骤模板)。

所有四个模板在如何部署 DACPAC 方面包含相同的基本功能,但是,它们是单独开发的,以避免给使用模板的人带来破坏性的变化。这篇文章使用了最新的模板,SQL Deploy DAC PAC with AAD support

所有四个模板都支持使用 SQL CMD 变量。因为可以有 N 个 SQL CMD 变量,所以模板中没有输入字段来定义它们。相反,模板代码在 Octopus 变量集合中查询以特定约定命名的变量:

  • SqlCmdVariable.Variable1
  • my.sqlcmdvariable.variable2

在部署过程中,当被添加时,您会看到如下内容:

填写模板字段:

  • dapacpackagename:包中.dacpac文件的名称。对于这个帖子,是sakila.mssql.dacpac.dacpac
  • (可选)发布概要文件名:发布概要 XML 文件的名称。对于这个帖子,它是sakila.mssql.dacpac.publish.xml
  • 报告:勾选这个框,生成一个 HTML 报告,报告将要进行的更改。
  • 脚本:勾选此框,生成一个包含将要执行的 SQL 的.sql文件。
  • 展开:勾选此框进行展开。
  • 将目标数据库提取到 dacpac :勾选此框,将目标数据库提取到 dacpac,并将其作为工件添加。
  • 目标服务器名称:数据库服务器的名称。
  • 目标数据库:要部署到的数据库的名称。
  • 目标数据库 DAC 版本:该列表是查找。dll 文件,选择 SQL Server PowerShell 模块来动态加载该模块。
  • 认证类型:认证选项列表,本帖使用 SQL 认证。
  • 用户名 : SQL 认证用户名。
  • 密码:SQL 认证用户的密码。
  • 启用多子网故障转移:是否使用多子网故障转移。
  • 附加部署贡献者:如果使用SqlPackage.exe /p:AdditionalDeploymentContributors=[what you would put here]命令行,您将添加的选项。
  • 附加部署贡献者参数:如果使用SqlPackage.exe /p:AdditionalDeploymentContributorArguments=[what you would put here]命令行,您将添加的选项。
  • DACPAC 包:要部署的包。
  • 命令超时:以秒为单位的超时,主要用于长时间运行的脚本。

您的部署将如下所示:

使用类似 SQL Server Management Studio (SSMS)的工具,您可以看到数据库已经更新:

结论

这篇文章向您展示了从项目创建到部署如何部署 DACPAC。请务必查看我们的示例实例中的一个示例。该示例部署到 Azure SQL 数据库服务器。

愉快的部署!

使用 dbatools 和 Octopus Deploy 部署 SQL Server 安全性

原文:https://octopus.com/blog/deploying-sql-server-security-with-dbatools-and-octopus-deploy

Deploying SQL Server Security with dbatools and Octopus Deploy

上个月,我写了关于您的 SQL Server 部署工具选项的文章。这个月,我将讨论管理数据库安全性这一经常令人痛苦且缺乏支持的挑战。

由于过去几周我一直在为我的一个客户解决这个难题,所以我将以一些 PowerShell 脚本的链接结束本文,您可以将这些脚本用于您自己的数据库。

安全性的挑战

如果您可以编写脚本,那么您应该对其进行源代码控制,并通过您的管道进行部署。

这使您可以看到发生了什么变化,什么时候发生的,为什么会发生变化,并且可以更容易地进行故障排除和回滚。审计员喜欢它。但是安全性带来了挑战,因为有些部分在每个环境中可能是相同的(例如,数据库角色),但其他部分可能会改变(例如,被分配到这些角色的用户)。

在每个环境的基础上编写脚本可能会导致大量的重复、管理上的辛劳和一大堆旧东西。然而,对所有环境使用一个标准的安全模型也是不可行的。显然,您的开发人员需要对他们的开发数据库进行写操作的权限,但是您可能不希望让每个开发人员都可以访问生产数据库。

实践基于角色的访问控制(RBAC)很有帮助。如果您的角色在整个管道中是一致的,那么您可以使用我上个月讨论的工具进行源代码控制和部署。然而,这仍然给你留下了管理每个环境中哪些用户应该是那些角色的成员的挑战。我在上一篇文章中讨论的工具通常不能提供一个优雅的解决方案——除了允许您过滤掉用户,以便您可以通过其他方式管理他们。

可能的解决方案

通常,您需要某种环境感知的部署后流程来部署适合环境的用户,并将他们分配到所需的角色。

Octopus Deploy 有一些很酷的特性来帮助您管理特定于环境的配置。例如,我们可以创建一个 #变量,并将其设置为开发生产,等等。在我们部署的每个环境中。然后,我们可以使用这个变量来选择是否要将 DEVPROD 用户部署到给定的环境中。例如,在下面的截图中,我将开发安全模型部署到开发测试环境中,但是我使用了不同的产品安全模型用于生产。

Example variable set to configure which security models should be deployed to each environment.

一些工具,如 SSDT红门工具,内置了对后期部署脚本的支持。作为我研究的一部分,我查看了彼得·肖特的一个项目的一些细节,他自己是在杰米·汤普森的早期工作的基础上建立的(谢谢彼得,你的工作真的帮助了我!).

然而,Peter 的工作只支持 SSDT 项目,正如我在上一篇文章中所讨论的,有很多部署工具,例如,我这个月接触的客户使用了不同的工具。我们真正想要的是不管你用的是 SSDT、Redgate、DbUp 还是别的什么都可以工作的东西。

自 2013 年(Peter 发表其作品)以来,PowerShell 已成为微软堆栈上的首选自动化工具,在过去几年中 dbatools (社区驱动的 SQL Server PowerShell 模块)彻底改变了热爱自动化的 DBA 使用 SQL Server 的方式。非常感谢克里斯·勒梅尔和所有其他人让我的工作变得如此简单!如果您热爱自动化,您使用 SQL Server,并且您没有听说过 dbatools,那么您真的需要了解一下。

dbatools 自带超过 500 个 cmdlet,包括(例如) New-DbaDbUserAdd-DbaDbRoleMember 。由于 PowerShell 是 Octopus Deploy 的默认自动化语言之一,dbatools 有助于我们避免动态生成 T-SQL 负载,因此我着手创建了一个纯 PowerShell 版本的 Peter 的 SSDT 后期部署脚本,该脚本易于维护,从 Octopus Deploy 运行起来也很简单,与您的数据库部署工具无关。

把所有的放在一起

我们想要的是一个环境感知、部署后脚本,可以很容易地从 Octopus Deploy 调用。我已经创建了一些 PowerShell 脚本来探索如何实现这一点。它们无论如何都不完美,但希望它们能给你一些启发。如果你喜欢,可以随意叉回购:https://github.com/Alex-Yates/DeploySqlServerSecurity

首先,一些免责声明。这仍是一项进行中的工作。我知道 PowerShell 足够危险,但不够优雅。使用时风险自担。

您要做的第一件事是运行 GetSecurity.ps1:

git clone https://github.com/Alex-Yates/DeploySqlServerSecurity.git
cd .\DeploySqlServerSecurity
\DeploySqlServerSecurity> .\GetSecurity -SqlInstance DevSql01 -Database MyDatabase -Environment Dev -OutputDir “C:\MyDatabase_security” 

这将把您的所有用户和角色成员导出到您选择的输出目录。

与 Peter 不同,我假设您正在练习 RBAC,所以我不包括角色或权限。我希望您的角色在所有环境中都是一致的,并且您的所有权限都是通过您的角色来管理的,并且这些角色是通过您的常规数据库部署工具来部署的。在这一点上,我也只支持 Windows 授权用户。如果这不现实,请告诉我,我们可以考虑添加角色、权限和/或 SQL 用户。

数据存储在 JSON 文件中。(抱歉 XML 粉丝。我计划很快添加 XML 支持。)我希望这些易于理解和维护:

Source files after running GetSecurity.ps1 the first time for the Dev environment.

现在,您可以第二次运行 GetSecurity.ps1,这一次将它指向测试数据库:

\DeploySqlServerSecurity> .\GetSecurity -SqlInstance TestSql01 -Database MyDatabase -Environment Test -OutputDir “C:\MyDatabase_security”

您将得到一个 users.json 文件,它指定了每个用户应该部署到哪个环境。对于每个环境,您还将获得一个单独的 rolemembers_$Environment.json 文件。角色成员文件定义了在特定环境中应该将哪些用户添加到哪个角色:

Source files after running GetSecurity.ps1 a second time, this time for the Test environment.

现在,您可以随意编辑这些文件。例如,您可以添加用户或更改哪些用户是哪个角色的成员。当您准备好时,您可以使用DeploySecurity.ps1部署用户。该脚本将对您的源代码执行一系列测试,并确保适当的角色和登录等。,然后它将部署您的用户并确保所有用户都被添加到适当的角色。

默认情况下,它不会删除任何内容,但是如果目标数据库中存在任何意外的用户或角色成员,它会向您发出警告。但是,如果您使用-DeleteAdditional开关,它将删除目标数据库中不存在于源文件中的任何内容。在本例中,我创建了一个几乎为空的数据库,只包含所需的角色,并在其中部署了我所有的 Dev 安全性。

\DeploySqlServerSecurity> .\DeploySecurity -SqlInstance localhost -Database EmptyDb -Environment Dev -SourceDir “C:\MyDatabase_security”

The output of deploying the Dev security model to an empty database with only the required roles pre-installed.

现在,您需要设置一个构建过程,将源文件和部署脚本打包到一个 NuGet 或 zip 文件中,并将它们传递给 Octopus Deploy。您可能希望运行TestSecurity.ps1作为构建过程的一部分,以检查您在编辑源文件时没有破坏键盘或者错过任何登录。

然后,在您的 Octopus 项目中,在常规数据库部署完成之后,您需要运行一个标准的 Octopus Deploy PowerShell 步骤,传递适当的环境变量,以确保将正确的安全配置部署到目标数据库。

An example Octopus Deploy project which deploys a NuGet package, then deploys an SSDT dacpac, and finally deploys the environment specific users and role members.

下一步是什么?

我有一堆东西想用它来做,我真的很想听听你对如何改进它的想法。你能使用这个吗?如果没有,为什么没有?少了什么?留给我一个 GitHub 问题或者提交一个 pull 请求。

然而,我也应该提到,在过去的几天里,我了解到斯图尔特·摩尔正在做类似的事情。本周早些时候,他向 sqlcolaboratory 发表了他的作品。他更多的是从安全审计/测试的角度来解决问题,而不是从源代码控制/部署的角度,但是我们的工作是如此紧密地联系在一起,所以最好是将我们的努力结合起来。

我最喜欢这个社区的一点是有多少人愿意分享他们的工作,并合作解决困难的问题。


自 2010 年以来,Alex Yates 一直在帮助组织将 DevOps 原则应用于他们的数据。他最引以为豪的是帮助 Skyscanner 开发了一天 95 次部署的能力,并支持了联合国项目服务办公室的发布流程。亚历克斯与除南极洲以外的各大洲的客户都有过合作——所以他渴望见到任何研究企鹅的人。

作为一名热心的社区成员,他共同组织了数据接力,是www.SpeakingMentors.com的创始人,并自 2017 年以来被公认为微软数据平台 MVP

Alex 是官方 Octopus Deploy 合作伙伴 DLM 顾问的创始人。他喜欢为那些希望通过改进 IT 和数据库交付实践来实现更好业务成果的客户提供指导、辅导、培训和咨询。

如果你想和亚历克斯一起工作,请发电子邮件:enquiries@dlmconsultants.com

使用 Octopus - Octopus Deploy 部署 SQL Server Integration Services(SSIS)包

原文:https://octopus.com/blog/deploying-ssis

Deploying SSIS with Octopus Deploy

当您想到自动化应用程序部署时,通常想到的是自动化部署 web 代码、容器和/或数据库。在本系列中,我将演示如何自动化支持组件,如 SQL Server Integration Services(SSIS)包和 SQL Server Reporting Services(SSRS)报表。


提取、转换和加载(ETL)流程通常由数据库管理员(DBA)或集成专家部署。在较低级别的环境中,允许开发人员直接部署到 SSIS 服务器并不罕见,但是当涉及到较高级别的环境时,比如生产环境,他们通常是手动完成的。DBA 有时可以运行脚本进行部署,但是它通常不包含在自动化应用程序部署的工具中。使用 Octopus Deploy,可以将 ETL 部署添加到部署堆栈中。

打造 SSIS 套餐

为了在部署过程中包含我们的 SSIS 包,您首先需要构建项目来生产用于部署的工件。没有一个流行的构建服务器可以开箱即用地构建 SSIS,并且需要一些配置。所有构建服务器的步骤大致相同。

您的 SSIS 项目必须在项目部署模型中,包部署模型将不适用于此解决方案。

配置生成代理

MSBuild 不知道如何生成 SSIS 项目类型。为了执行构建,您需要配置构建代理。

可视化工作室

要构建 SSIS 项目,我们需要在构建代理上安装 Visual Studio。Visual Studio 的社区版应该够用了。

请咨询社区版的 EULA,以确保您或您的组织可以使用它。

在我们的构建代理上有了 Visual Studio 之后,我们需要安装一个名为 SQL Server 数据工具(SSDT)的扩展。安装完成后,我们的构建代理将能够进行构建。dtsproj 项目。

构建 SSIS 任务

默认情况下,大多数(如果不是全部的话)构建服务器没有构建 SSIS 的任务。您需要安装或配置自定义任务来执行构建。

azure devo PS/Team Foundation Server

Azure DevOps (ADO)或 Team Foundation Server (TFS)的市场有社区创建的几个任务。如果你用的是 ADO/TFS,我建议用这个:

团队城市

TeamCity 没有可用于执行构建的插件,但是 Pavel Hofman 有一篇关于如何在 TeamCity 上配置构建 SSIS 项目的很棒的博客文章

詹金斯

与 TeamCity 类似,Jenkins 没有任何插件可用于执行 Visual Studio 构建。然而,执行类似于 TeamCity 解决方案的步骤应该会产生相同的结果。

构建项目

在这篇文章中,我使用 Azure DevOps 来执行构建。

构建任务

填写构建任务详细信息:

包装工件

SSIS 项目建成后,将产生一个。ispac 文件,其中包含部署所需的组件。。ispac 不像。拉链还是。nupkg,所以需要一个额外的步骤将其打包成支持的格式。对于 ADO、TeamCity 和 Jenkins,Octopus Deploy 具有插件或扩展,其中包含执行以下操作的步骤:

将工件推送到存储库

之后。ispac 文件已经打包,您需要打包它并将其发送到一个存储库,例如:

  • Octopus 部署内置存储库
  • 艺术工厂
  • 关系
  • ADO/TFS 知识库

章鱼部署

现在我们已经准备好了包,我们可以创建和配置我们的 Octopus Deploy 项目了。

创建项目

要创建我们的项目,请单击项目添加项目:

添加 SSIS 部署步骤

用于部署 SSIS 包的唯一步骤模板在社区步骤库中。点击添加步骤:

按 SSIS 过滤将显示可用的 SSIS 步骤模板。对于这个演示,我使用Deploy ispac SSIS project from Referenced Package。该模板将允许我们使用工作进程进行部署,而不是在 SSIS 服务器上安装代理。

填写步骤细节

这一步允许我们在部署目标或工作者上运行这一步。在这个演示中,我使用了一个工人,所以我们不需要在 SSIS 服务器上安装触手。展开Execution Location部分并选择Run once on a worker

现在填写参数:

  • 数据库服务器名称(\实例):要连接的 SSIS 服务器的名称。(即 SSISServer1 或 SSISServer1\Instance1)。
  • SQL 认证用户名(可选):SQL 账户用户名。留空以使用集成身份验证。
  • SQL 认证密码(可选):SQL 账户的密码。留空以使用集成身份验证。
  • 启用 SQL CLR:SQL Server 的 SSISDB 特性要求启用 SQL CLR。如果该功能尚未启用,请将其设置为 true。
  • 目录名称:ssis db 的目录名称,建议不要更改该值。只有在尚未安装 SSISDB 功能的情况下,才需要这样做。
  • 目录密码:ssis db 目录的密码。只有在尚未安装 SSISDB 功能的情况下,才需要这样做。
  • 文件夹名称:SSISDB 目录中放置 SSIS 项目的文件夹名称。
  • 项目名称:SSIS 项目名称。该名称必须与 Visual Studio 中的项目名称完全匹配。
  • 使用环境:如果要在 SSISDB 中使用环境变量,设置为 true。
  • 环境名:要使用的环境名。
  • 将项目参数引用到环境变量:设置为 true,将项目变量链接到环境变量。
  • 将包参数引用到环境变量:设置为 true,将包变量链接到环境变量。
  • 使用全限定变量名:为真时,包变量名必须用dtsx_name_without_extension.variable_name表示。
  • 为连接管理器属性使用自定义过滤器:自定义过滤器应包含正则表达式,用于在自动映射期间进行设置时忽略属性。
  • 连接管理器属性的自定义过滤器:在自动映射过程中过滤连接管理器属性的正则表达式。当UseCustomFilter设置为真时使用该字符串。
  • 包 ID :用于部署的包的 ID。
  • 包 Feed ID :包所在的 Feed 的 ID。

填写完表单后,我们现在可以部署我们的包了。

部署

让我们创建我们的发布。点击创建释放按钮:

点击保存:

T35【

选择要部署到的环境:

然后确认部署:

我们的包已经部署:

部署日志

如果您已经将项目参数引用到环境变量中,您会在部署日志中注意到如下内容:

- Adding environment variable CM.WWI_Source_DB.ConnectionString
**- OctopusParameters collection is empty or CM.WWI_Source_DB.ConnectionString not in the collection -** 

这条消息表明您已经引用了一个环境变量的项目参数,但是在 Octopus Deploy 项目变量中没有找到变量CM.WWI_Source_DB.ConnectionString。这意味着您可以创建一个同名的 Octopus Deploy 项目变量来控制从一个环境到另一个环境的值,就像部署应用程序一样。

查看结果

让我们使用 SQL Server Management Studio (SSMS)来看看我们的 SSISDB:

打开环境,我们看到我们的变量已经创建:

结论

在这篇文章中,我演示了如何使用 Octopus Deploy 部署 SSIS 包。使用这种方法,您现在可以使用相同的工具来包含支持应用程序组件。

使用 Octopus - Octopus Deploy 部署 SQL Server Reporting Services(SSRS)报表

原文:https://octopus.com/blog/deploying-ssrs

Deploying SSRS with Octopus Deploy

当您想到自动化应用程序部署时,通常想到的是自动化部署 web 代码、容器和/或数据库。在本系列中,我将演示如何自动化支持组件,如 SQL Server Integration Services(SSIS)包和 SQL Server Reporting Services(SSRS)报表。


收集和存储数据通常是 web 应用程序的主要功能。需要分析收集到的数据,并以图形格式显示出来,以帮助做出决策。这通常采用报告的形式。SQL Server Reporting Services(SSRS)是微软的报告解决方案。SSRS 是一个基于 web 的应用程序,可以使用许多不同的数据源来为用户填充报告。在本系列的这一部分中,我将演示如何使用 Octopus Deploy 部署 SSRS 报表。

构建项目

截至 Visual Studio (VS) 2017,MSBuild 可以构建。rtpproj 文件。在 Visual Studio 的旧版本中创建的项目将需要配置类似于我的 SSIS 邮报的构建代理,以便使用 Visual Studio itslef(devenv . exe)来构建项目。这篇文章利用 Azure DevOps 作为构建平台,但是任何可以使用 MSBuild 的构建服务器都可以做到这一点(针对 VS 2017+)。

添加构建任务

要生成 SSRS 项目,只需将任何 MSBuild 类型的任务添加到您的生成定义中。对于我的构建定义,我选择了一个 Visual Studio 构建任务,它调用 MSBuild:

打包工件

当构建 SSRS 项目时,它将构建的报告放入bin文件夹中。在本例中,他们在Reports/Reports/bin/release We 中将这些报告打包成一个. zip 或. nupkg 包进行部署。对于这篇文章,我选择。nupkg 格式:

推动藏物

既然我们已经打包了工件,我们可以将它们推送到我们的 Octopus Deploy 服务器:

这就是我们构建定义所需要的。

章鱼部署

现在,我们已经在 Octopus Deploy 中拥有了我们的工件,我们可以为部署创建我们的项目。

创建项目

点击项目,然后添加项目:

部署包

我们流程的第一步是将包部署到 SSRS 服务器。点击添加步骤:

添加一个部署包步骤,并选择我们推送到 Octopus 服务器的包:

添加步骤后,填写文本框:

添加 SSRS 步骤

为了部署 SSRS 报告,我添加了一个社区步骤模板。按 SSRS 筛选步骤,并从程序包步骤中选择部署 SSRS 报表:

此步骤需要在部署包的同一目标上执行,对于执行位置,选择部署目标并填写您在部署包步骤中选择的相同角色:

填写该步骤的值。

  • SSRS 包步骤:这是我们之前创建的部署包步骤。
  • SSRS 服务器服务的 URL:这是报表服务 web 服务的 URL。比如 http://servername/ReportServer/reportservice 2010 . asmx?wsdl。
  • 报表执行 Url :这是报表执行 web 服务的 Url。比如 http://servername/ReportServer/report execution 2005 . asmx?wsdl。
  • 报表文件夹:报表将要部署到的文件夹的相对路径。比如/MyFolder
  • 报表数据源文件夹:数据源将要部署到的文件夹的相对路径。比如/MyFolder/MyDataSources
  • 覆盖数据源:如果您想在部署时覆盖数据源,请选中此项。

要指定数据源细节,您需要创建 Octopus Deploy 变量来匹配要覆盖的数据源。可以覆盖以下属性:

  • 连接字符串
  • 用户名
  • 密码

例如,如果数据源的名称是MyDatasource,变量将是:

  • 我的数据源。连接字符串
  • 我的数据源。用户名
  • 我的数据源。密码

如果用户名和密码用于一个域帐户,您需要创建一个名为MyDatasource.WindowsCredentials的额外变量,其值为True

  • 备份位置(可选):在覆盖报表之前,在部署目标上备份报表的位置。
  • 数据集文件夹(可选):共享数据集将要部署到的文件夹的相对路径。比如/MyFolder/MyDatasets
  • 报表部件文件夹(可选):报表部件将要部署到的文件夹的相对路径。比如/MyFolder/MyReportParts
  • 服务域(可选):部署时使用的账户域名。
  • 服务用户名(可选):部署时使用的帐户用户名。
  • 服务密码(可选):用户部署时使用的密码。
  • 清除报告文件夹:如果您想在部署之前从目标文件夹中删除报告,请选中此框。
  • 使用包文件夹结构:如果您希望该步骤遵循包中包含的文件夹结构,选中此框。该设置忽略Report folderReport data source folderDataSet folder设置。
  • 根文件夹:专门与Use package folder structure选项一起使用,指定这个项目在 SSRS 文件夹中的根位置。

步骤到此为止。我们现在准备部署。

部署报告

流程完成后,让我们创建一个发布。点击创建发布:

点击保存后,选择部署环境:

点击部署:

部署完成后,它应该如下所示:

T31

结论

在这篇文章中,我演示了使用 Octopus Deploy 部署 SSRS 报表是多么容易。愉快的部署!

使用 Octopus Deploy - Octopus Deploy 将 TeamCity 部署到 Kubernetes

原文:https://octopus.com/blog/deploying-teamcity-to-kubernetes

Octopus deploying a TeamCity container to Kubernetes illustration

嗨!我叫 Bob Walker,是 Octopus Deploy 的解决方案架构师。

我的主要关注点是确保我们的客户成功使用 Octopus Deploy。这句简单的话涵盖了广泛的职责。有一天我可能会审查配置。接下来,我可能会使用 API 进行自定义实现。或者,我可能会提供演示,展示我们添加的所有新功能。在 Octopus Deploy,我们对客户使用哪种构建服务器没有偏好,这种灵活性对我们客户来说非常大,因为他们可以继续使用他们选择的构建工具。这样做的一个副作用是,我可以通过创建构建来演示来学习如何使用每个构建服务器。说到这里,如果您想要一个演示,请点击此处预定一个!

在撰写本文时,我们还有两位专注于客户成功的解决方案架构师,Ryan Rousseau 和 Derek Campbell。只要有可能,我们就分享我们制作的演示。我们的背景是开发或基础设施,这意味着我们真的不喜欢任何时候我们必须重新发明轮子。我们发现共享演示资源的最简单方法是尽可能使用 SaaS 或 IaaS。我们使用 Octopus Cloud 作为演示 Octopus 实例。事实上,我们是章鱼云的 alpha 用户之一,也是最前沿的用户之一。我们每天帮狗粮章鱼云。

我们最初的两个构建服务器,VSTS(现在的 Azure DevOps)和 AppVeyor,非常容易安装。他们已经是 SaaS 了。但是我们的许多用户都在使用 TeamCity。我们三个都有一个本地 TeamCity 实例。是时候把它转移到云端了。

我选择 TeamCity 的另一个原因是因为它的“现实世界”潜力。我有机会使用的许多容器都相当简单。一个 ASP.NET 核心 WebApi,它只连接到一个外部 SQL 服务器。对于 TeamCity,有许多考虑因素,有一个主服务器,一个需要与服务器通信的代理,以及在部署之间持久化数据的需要。

旁注:在本文中,我有时会交替使用 Kubernetes 和 K8s。它们的意思是一样的。这真的取决于我写这个句子时的感受。

要求

我们希望我们的构建服务器支持许多技术。该列表包括但不限于:

  1. 。NET Framework 4.7.x 应用程序(ASP。NET、Windows 服务等)
  2. 。网络核心应用
  3. Windows 容器应用程序
  4. Linux 容器应用程序(适用于我的。网络核心应用!)
  5. Java 语言(一种计算机语言,尤用于创建网站)
  6. Web 框架(Angular、React 等)

平台

我可以走捷径,在一个 Linux 虚拟机上运行 TeamCity,在另一个虚拟机上运行 Windows build agent。JetBrains 提供 AWS 和 Azure 模板来尽可能简化设置。但是我不想维护虚拟机。

JetBrains 提供了一个服务器停靠容器和一个代理停靠容器。如果仔细观察代理 docker 容器,您会发现它既可以作为 Linux 容器运行,也可以作为 Windows 容器运行。它们还包括许多构建工具,如。NET 和 Git。而且,Octopus Deploy 最近增加了 Kubernetes 支持。今天的挑战是让 TeamCity 在 Kubernetes 集群中运行。

出于好玩,我将使用 Octopus Deploy 将 TeamCity 部署到 Kubernetes,这将依次将包推回到同一个 Octopus Deploy 实例。那是一些蛇在吃尾巴,但为什么不呢?YOLO,对吗?

步骤 1:创建 K8s 集群并连接 Octopus Deploy 到它

首先,我们需要设置 Octopus Deploy 来部署到 K8s 集群。为了实现这一点,我需要首先创建一个 K8s 集群。出于本文的目的,我将使用谷歌云平台或 GCP 来托管我的 K8s 集群。

让我们去谷歌控制台登录

创建集群

创建您的帐户并登录后,您将被发送到控制面板。在左侧菜单中选择 Kubernetes。

单击创建集群按钮。

您将看到一个向导。您可以保留默认值不变。我所做的只是输入一个名字,然后点击创建。我只留下了大小(1 个 vCPU 和 3.75 GB 的内存)和位置(us-central1-a)。有趣的是,那个数据中心离我家只有 15 英里。如果有任何延迟,我有几个问题和几个后续问题。

设置 Octopus 以连接到 Kubernetes 集群

创建群集大约需要 5 到 20 分钟。在我们等待的时候,让 Octopus Deploy 准备好连接它。为此,我将使用我团队托管的 Octopus 实例。因为我使用的是托管实例,所以我知道我使用的是最新版本,在撰写本文时是 2018.8.6。您至少需要使用 2018.8.0 才能正常工作。

目前 K8s 被一个功能标志禁用。首先,转到配置➜功能并启用它。

接下来,是时候创建一个工人了。工人是一种新型的目标。这个特性是 2018.7.0 新增的。它允许您创建一个机器池来执行工作。以前这项工作是直接在 Octopus 服务器上进行的。

工人将在其上安装 Kubectl。我希望这些机器与我的其他目标分开,因为它们将对我的集群拥有管理员权限。为此,我首先创建了一个工人池,名为“Kubernetes 工人池”

工人是没有什么花哨的,一个监听触手分配到一个特定的工人池。

别忘了在机器上加上 KubeCtl。我用的是 Windows,所以我会让 chocolatey 来处理繁重的工作。

Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

choco install kubernetes-cli -y 

连接到 GCP

好了,我们已经消磨了足够的时间。让我们检查一下 GCP,看看我的集群在哪里。很好,完成了。点击铅笔图标。

下一页向我们展示了集群的概况。记下 IP 地址。这就是 Octopus 与集群通信的方式。在你疑惑之前,“嘿,显示你的 IP 地址和其他敏感信息难道不危险吗?”是的,它是。在这个上线之前我删除了 GCP 的所有内容。

补充说明:在写这篇文章的时候,我确实重复了几次这些步骤。我在尝试几个选择。这些截图来自很久以前被删除的原始实例。

为了连接到集群,我们需要获得用户名和密码。

获取管理员凭据以将 Octopus 连接到 Google Cloud

Google Cloud 上的 Kubernetes 提供了两种连接方式。在撰写本文时,谷歌云将为您创建一个管理员帐户。然而,在未来的版本中,这将是可选的。

如果您单击“显示凭据”链接,将会显示用户名和密码。

补充说明:在 Google Cloud 上即将发布的 Kubernetes(1.12 版)版本中,默认的管理员和密码将被禁用。您需要创建一个服务帐户。请按照这些说明进行操作。由于 1.12 版还没有发布,我在这里发布的任何内容都可能会过时。

保存凭据

现在我们有了用户名和密码,我们将把这些凭证保存在 Octopus 中。转到基础设施➜账户。在右上角,点击添加帐户,并选择用户名/密码。

你需要做的就是在 GCP 输入用户名和密码。

保存证书

将使用 GCP 提供的证书,因此我们可以使用 TLS 验证。我们需要在 Octopus Deploy 中保存该证书,以便使用。首先,将证书作为. pem 文件保存到桌面。然后转到库➜证书并点击添加证书。

填写表单并选择您在桌面上创建的证书文件。

如果成功,你会看到一个类似这样的屏幕。您会注意到证书是自签名的,并将在 5 年后过期。如果我想的话,我可以设置一个订阅,在过期时通知我。但不是现在,那是未来鲍勃的问题。

添加 Kubernetes 目标

现在是时候添加 Kubernetes 部署目标了。转到添加部署目标

首先,创建一个名称,将其分配给环境和角色。

接下来,选择用户名/密码帐户,输入 IP 地址,然后选择证书。

请注意:在 URL 的[Ip 地址]前有 https://很重要。如果你不这样做,Octopus 将无法连接到你的 K8s 集群,你会花很多时间想知道为什么。我知道这一点,因为我忘记了,我一直挠头想知道为什么它不工作。

我喜欢马上进行健康检查,以确保我没有搞砸任何事情。

添加外部 Docker Feed

最后,我们需要添加 Docker Hub 作为提要。这是 TeamCity 容器将被拉出的地方。转到➜图书馆外部源并点击右上角的“添加源”按钮。

步骤 2:部署 Team City Server

我想从简单开始,然后变得复杂。可以将 TeamCity 配置为使用外部存储和外部数据库。但是也可以将其配置为使用本地存储和本地数据库。我知道如果我将它配置为使用本地资源,那么每次进行部署时它们都会被销毁。就目前而言,这很好。我只想让它跑起来。我不会设置任何用户或项目。

创建项目并添加第一步

在 Octopus Deploy 中,创建一个新项目。我创建了一个名为“启动”的新环境和名为“仅启动”的生命周期您可以随意配置您的环境和生命周期。这正是我所做的。

该过程将包括一个部署 Kubernetes 容器的步骤。请注意,这一步有很多选项。为了简洁起见,我将只包括我更改的项目的截图。如果你在截图中看不到什么,假设我没动它。

部署 Kubernetes 容器步骤

首先,输入这一步的基本信息。

接下来,点击“配置资源”并禁用“秘密”和“配置地图”这是为了让这个步骤更容易完成。

输入部署名称。该名称只能包含小写字母数字字符和“-”。

对于部署策略,我将它留在“重新创建部署”中。

现在是指定容器的时候了。点击添加容器。

完整的容器名是 jetbrains/teamcity-server。JetBrains 是创建容器的用户名,teamcity-server 是容器的名称。将所有信息输入到“添加容器”屏幕的开始部分。

现在是时候指定端口号了。TeamCity 公开的默认端口号是 8111。

这就是我们现在要指定的全部内容。继续并点击 ok 按钮。

模式窗口关闭后,在功能选择中输入服务的名称,并选择负载平衡器选项。

我们需要进入服务器的方法。单击添加端口按钮,打开服务端口模式窗口。

为此,我们将使用端口 80。给它一个名称,使用端口 80,并让它指向我们选择容器时定义的命名端口。

完成后,摘要屏幕应该类似于以下内容:

首次部署

就是这样!现在是保存的时候了,我们可以创建我们的第一个部署了!

部署本身并不需要很长时间。

但是回到 GCP 的界面,点击左边的服务链接。您可以看到,GCP 需要一分钟来创建负载平衡器。

负载平衡器完成后,让我们单击 URL。如果一切顺利,我们应该看到这个屏幕!

第三步:持久卷

好吧,事情有进展了。然而,我们有一个小问题。每次 Octopus Deploy 进行部署时,它都会销毁节点并重新创建它。这意味着上面截图中的数据目录将被清除。最终结果是,我们每次都必须重新创建 TeamCity 配置。即...太可怕了。那是什么样的构建服务器?

请注意:这不是由 Octopus Deploy 引起的,这是 Kubernetes 的一个特点。默认情况下,它假设 pod 可以在需要时被销毁和重新创建。

我们需要的是在两次部署之间持久保存这些数据。幸运的是,Kubernetes 已经具备了这种功能。我们只需要利用它。

为了简单起见,我将使用一个简单的持久性卷。这使用支持 Kubernetes 集群的存储。可以利用其他存储选项。有多种选择。选择最适合贵公司/需求的方案。

将创建持久卷步骤添加到流程中

要做到这一点,我们需要学习一点关于 K8s CLI 的知识,这个 CLI 叫做 kubectl。我们感兴趣的命令是应用命令。如果资源不存在,此命令将创建一个资源。这是完美的,这意味着我们可以在部署过程中使用它。

现在,apply 命令的一个奇怪之处是,您必须为它提供一个 YAML 或 JSON 文件。该文件可以是 URL 或硬盘上的权限。我创建了一个 GitHub repo 来存储这些类型的文件。然后我参考了 Github 的文件。

文件。它正在为我创建一个 30 GB 的硬盘驱动器,用作我的数据驱动器。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dt-pm-claim
  labels:
    app: data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 30Gi 

Octopus Deploy 可以运行 kubectl 命令。让我们继续寻找要添加到我们流程中的步骤。

我需要运行的脚本是:

kubectl apply -f [URL of file] 

让我们继续将它添加到步骤中。不要忘记设置工人和角色!

保存后,我的流程是这样的。

这看起来不对,我们想要为服务器创建卷。让我们重新订购那个。

添加持久卷以部署 TeamCity 服务器步骤

现在,我们需要告诉 TeamCity 服务器我们将要创建的存储。返回到部署 TeamCity 服务器步骤。找到“volumes”部分,然后单击“add volume”。

从下拉列表中选择永久卷声明。在接下来的两个文本框中,输入您在 YAML 文件中创建的名称。

现在,这个部署知道这个声明。我们需要告诉集装箱这件事。单击容器定义。

我们需要在这个容器中添加一个卷装载。这可以通过展开卷装载并单击“添加卷装载”链接来完成。

提供卷装载的详细信息。我选择路径/mnt/teamcity/data 的唯一原因就是“为什么不”

卷已全部装入。现在我们需要告诉 TeamCity 默认使用这个卷装载。

要设置的环境变量的名称是什么?多亏了一些 GoogleFu 和一些随机的文章,我能够确定它应该是“团队城市数据路径”所以我们来设定一下。

在您单击 ok 之后,您的部署摘要应该如下所示。

部署

好了,是时候进行另一次部署了!创建卷不需要太长时间。它应该显示部署成功。

当我转到我在 Kubernetes 中的 TeamCity 实例时,我看到路径现在已经更改为“/mnt/teamcity/data。”成功!

步骤 4:配置团队城市

TeamCity 正在运行的数据量将在两次部署之间持续存在。现在可以安全地配置 TeamCity 了。我的建议是做一些小的配置。例如设置数据量、设置数据库和创建管理员用户。只需进入 TeamCity 仪表板主屏幕。然后用 Octopus Deploy 再释放一次。您的所有设置应该保持不变。如果他们不这样做,那么你就知道有些东西配置不正确。在确定数据将在 Kubernetes 部署之间保持不变之前,不要配置任何项目。那会让你失去一天的乐趣。

步骤 5:添加生成代理

没有构建代理的构建服务器有什么用?好的方面是 JetBrains 提供了一个我们可以利用的好的构建代理映像。它提供了相当多的内置功能。

将部署生成代理步骤添加到流程中

现在只需添加一个 Windows 构建代理并开始使用即可!我正在创建一个新步骤来安装构建代理。我这样做有几个原因。第一,它有助于在出现问题时进行调试。第二,我可以自由地将它移动到另一个群集或同一群集内的另一个节点。

我将对几乎所有内容使用默认值。我为部署命名。但我会继续重建部署。

容器相当简单,只需将它指向 jetbrains/teamcity-agent 图像。因为这是内部生成代理,所以不需要公开端口。

代理的环境变量将告诉它指向哪个服务器以及应该使用什么名称。关于 Kubernetes 的一个巧妙的小技巧是,您可以引用 TeamCity 服务器的服务名,Kubernetes 将处理其余的工作。

因为这是一个内部构建服务器(意味着我们不希望人们从外部连接到它),所以我不需要设置任何服务名或端口。

现在我的过程有三个步骤,一个是创建卷(如果它不存在),另一个是创建服务器,最后一个是创建代理。

重新部署到 TeamCity 群集

是时候创建另一个版本了。令人失望的是,您必须让 Octopus 告诉 Kubernetes 部署代理的 Windows 映像,而不是代理的 Linux 映像。

同样,部署不需要太长时间。一切都是绿色的。

如果您在部署期间打开了 TeamCity,您将会看到这样的消息。

缺少生成代理

在等待 TeamCity 启动几分钟后,我没有看到代理。等了 10 分钟代理还是没有出现。

好吧,让我们检查一下库伯内特的节点。确保一切部署正确。所有节点都显示为绿色。

是时候深入挖掘一下了。让我们来看看每个节点的 pod。单击第一个节点显示问题。TeamCity 代理显示 ImagePullBackOff 错误。不管那是什么意思。

如果我单击显示详细信息,我会看到完整的错误消息。Kubernetes 无法提取图像。什么?为什么?

我就不告诉你细节了。这里是 TL;DR;它的版本。这些节点正在运行容器优化的操作系统。

单击“更改”链接会显示我有哪些有限的选项。

更改为 Linux 构建代理容器

为了部署和运行 Windows 容器,底层操作系统必须是 Windows。这难道不是一个有趣的小怪癖吗?为了获得额外的乐趣,让我们尝试重新部署,但只使用默认映像,即 Linux。

Octopus Deploy 说它是成功的。

在等待 TeamCity 完成启动后,我现在看到了一个需要授权的代理。

结论

在撰写本文时,Kubernetes Windows 支持仍处于测试阶段。事实上,没有一个主要的云提供商支持在他们的 Kubernetes 实现中运行 Windows 容器。甚至没有蔚蓝。如果 Azure 不支持它,Google Cloud 或 AWS 支持它的可能性非常小。

在完成所有工作和配置后,我遇到了另一个问题。在容器中构建容器映像。为此我需要访问 docker 守护程序。这样做的文档非常好。问题是我一直在碰壁,试图让一切配置正确。

【T2

这是什么意思?我的目标是在 Kubernetes 集群上运行整个构建服务器。这是不可能的。现在,这是我可以用现有的 TeamCity 集群构建的。

  1. 。NET Framework 4.7.x 应用程序(ASP。NET、Windows 服务等)
  2. 。网络核心应用
  3. Windows 容器应用
  4. Linux 容器应用程序(针对我的。网络核心应用!)
  5. Java 语言(一种计算机语言,尤用于创建网站)
  6. Web 框架(Angular、React 等)

总的来说,这不是世界末日。这仍然是相当多的功能。它不符合我的所有要求。但是没关系。这是一次很好的学习经历。把这些知识传播出去对我来说很重要。Kubernetes 和 Docker 都很棒。他们可以解决很多问题。然而,它们不是灵丹妙药。此时了解工具的局限性是很重要的。决不,我是我试图抨击平台。每个平台都有局限性。一旦你知道这些限制,就更容易使用它们。

您可以在 Kubernetes 集群中运行 TeamCity。事实上,它很容易安装。但是,如果在虚拟机上运行 TeamCity,您将无法获得完整的功能。根据您的使用情况,这可能好,也可能不好。

好吧,我希望你对 Kubernetes 和 Octopus Deploy 有更多的了解。愉快的部署!

了解更多信息

使用 GitHub Actions 和 Octopus - Octopus Deploy 部署到 Azure

原文:https://octopus.com/blog/deploying-to-azure-with-github-actions-and-octopus

GitHub Actions 是一个持续集成和持续交付(CI/CD)工具,它使用自动化操作来部署您的代码。如果您将代码存储在 GitHub 中,GitHub Actions 会用 CI/CD 功能增强每个 GitHub 存储库,从而简化部署。

许多开发人员希望有一种简单的方法来入门,而不需要臃肿的企业工具,GitHub Actions 满足了这种需求。

在这篇文章中,我将向您展示如何开始使用 GitHub Actions,以及如何使用 Octopus 将一个示例 web 应用程序部署到 Azure。

开始之前

要完成这篇文章中的步骤,你需要:

部署流程从 GitHub 开始。GitHub 托管 web 应用程序代码。GitHub Actions 自动检测代码库的变化,构建代码,并将 Docker 映像部署到 Docker Hub。Octopus Deploy 在编排步骤中使用这个映像将 web 应用程序部署到 Azure。

这个过程的第一步是分叉随机报价库。Random Quotes 是一个简单的 web 应用程序,它生成随机的历史报价,以展示 GitHub Actions 的功能。

接下来,您需要设置 GitHub 动作来自动化构建、推送和部署过程。为此,您需要从 Docker 和 Octopus 中检索一些凭证。

转到 Docker Hub 账户,然后是账户设置,然后是安全,创建一个新的访问令牌。请确保保存此令牌,因为您只能查看一次。

Docker Token

转到你的 Octopus 实例,然后配置文件,然后我的 API 密匙并创建一个 API 密匙。保存该键值。记下你的八达通服务器网址。

Octopus API key

在你的分叉随机报价存储库中,进入设置然后秘密、然后动作,添加以下存储库秘密:

DOCKER_HUB_ACCESS_TOKEN
DOCKER_HUB_USERNAME
OCTOPUS_APIKEY
OCTOPUS_SERVER 

导航到。GitHub/workflows,您可以在其中看到 node.yml 文件。这个文件指导 GitHub 如何部署代码。用以下代码替换文件的内容:

name: deploytoazure

on:
  push:
    branches: [ master ]
jobs:

  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Check Out Repo 
        uses: actions/checkout@v2

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v1

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          context: ./
          file: ./Dockerfile
          push: true
          tags: ${{ secrets.DOCKER_HUB_USERNAME }}/randomquotes-js:latest 

这段代码在每次新的 push to main 时构建代码并将其作为 Docker 映像推送到 Docker Hub。转到 GitHub 动作选项卡查看步骤。

【T2 GitHub Success Initial

构建完成后,导航到 Docker Hub 查看图像。

配置 Azure 帐户

您需要配置一个 Azure 帐户和 web 应用程序作为 Octopus 部署的目标。其他目标也是可能的,例如 AWS 或 local。

接下来,通过导航到 Azure 门户,在 Azure 中创建一个帐户。

使用 Azure 门户创建 Azure 服务主体

  1. 在 Azure 门户中,打开菜单 hamburger menu 并导航到 Azure Active Directory,然后是属性,并从租户 ID 字段中复制值。这是你的房客身份证。
  2. 接下来你需要你的应用 ID
  • 如果您创建了一个 AAD 注册的应用程序,导航到 Azure Active Directory、然后应用程序注册,点击查看所有应用程序,选择应用程序并复制应用程序 ID 。请注意,Azure UI 默认为自有应用标签。点击所有应用选项卡查看所有应用注册。
  • 如果您尚未创建注册的应用程序,请导航至 Azure Active Directory、然后应用程序注册,点击新注册并添加您的应用程序的详细信息,然后点击保存。记下应用 ID
  1. 通过导航到证书&机密、然后证书&机密来生成一次性密码。添加新的秘密,输入描述,点击保存。记下显示的应用程序密码,以便在 Octopus 中使用。如果您不想接受默认的密码一年到期,您可以更改到期日期。

您现在拥有以下内容:

  • 租户 ID
  • 应用 ID
  • 应用程序密码/秘密

现在,您可以在八达通中添加服务主账户。

接下来,您需要配置您的资源权限。

资源权限

资源权限确保您注册的应用程序有权使用您的 Azure 资源。

  1. 在 Azure 门户中,导航到资源组并选择您希望注册的应用程序访问的资源组。
  2. 接下来,点击访问控制(IAM) 选项。在角色分配下,如果你的应用没有列出,点击添加角色分配。选择适当的角色(贡献者是一个常见选项),并搜索您的新应用程序名称。从搜索结果中选择它,然后单击保存

接下来,您将设置一个 Azure web 应用程序并配置其属性。

Web 应用程序设置

  1. 如果一个资源组不存在,通过转到主页,然后资源组,然后创建来创建一个。创建之后,记下资源组的 Azure 订阅 ID。
  2. 在您的资源组中,点击创建,然后点击 Web App
  3. 对于发布设置,选择 Docker 容器。
  4. 对于操作系统,选择 Linux。
  5. 记下你的 Azure 应用名称。这将是您的 web 应用程序的地址:[webapp-name].azurewebsites.net
  6. 设置应用程序时,请注意应用程序服务计划和资源组。

在您的 Octopus 实例中,进入,然后进入外部提要,通过输入您的 Docker 凭证添加 Docker 容器注册表提要。点击保存并测试确认连接。

在八达通上加入服务主账户

使用以下值,您现在可以将您的帐户添加到 Octopus:

  • 应用程序 ID
  • 租户 ID
  • 应用程序密码/密钥
  1. 导航至基础设施、然后是账户
  2. 选择添加账户,然后选择 Azure 订阅
  3. 在 Octopus 中给帐户起一个你想要的名字。
  4. 给账户一个描述。
  5. 添加您的 Azure 订阅 ID。这可以在 Azure 门户的订阅下找到。
  6. 添加应用 ID租户 ID应用密码/关键字

点击保存并测试确认账户可以与 Azure 交互。Octopus 然后尝试使用帐户凭证来访问 Azure 资源管理(ARM) API,并列出该订阅中的资源组。您可能需要将目标 Azure 数据中心的 IP 地址列入白名单。请参见通过防火墙部署到 Azure】了解更多详细信息。

新创建的服务主体可能需要几分钟才能通过凭据测试。如果您已经仔细检查了您的凭据值,请等待 15 分钟,然后重试。

添加部署目标

使用 Octopus,您可以将软件部署到 Windows 服务器、Linux 服务器、Microsoft Azure、AWS、Kubernetes 集群、云区域或离线软件包。无论您在哪里部署软件,这些机器和服务都是您的部署目标。Octopus 将您的部署目标(您部署软件的虚拟机、服务器和服务)组织到环境中。

  1. 转到基础设施、然后是部署目标
  2. 选择一个 Azure Web 应用。
  3. 输入显示名称。
  4. 填写环境目标角色
  5. 选择之前创建的 Azure 帐户和 web 应用。

创建项目环境

通过导航到项目、然后添加项目来创建项目。这些步骤假设一个名为docker的项目。

通过转到基础设施,然后环境,然后添加环境,添加一个名为Production的环境。

导航到您创建的项目。在变量下,添加以下变量及其值:

  • app-service-plan
  • resource-group
  • webapp-name

在流程步骤中,添加一个 Azure 脚本步骤。

Azure script

添加您之前配置的 Azure 帐户,并将以下代码复制到脚本步骤中:

 $pi = $OctopusParameters["Octopus.Action.Package[randomquotes-js].PackageId"]
 $pv = $OctopusParameters["Octopus.Action.Package[randomquotes-js].PackageVersion"]
 $dockerImage = "${pi}:${pv}"
 $ImageName = $OctopusParameters["Octopus.Action.Package[randomquotes-js].Image"]
 $RegistryUrl = $OctopusParameters["Octopus.Action.Package[randomquotes-js].Registry"]

 az webapp create --resource-group $OctopusParameters["resource-group"] --plan $OctopusParameters["app-service-plan"] --name $OctopusParameters["webapp-name"] --deployment-container-image-name $dockerImage

 az webapp config container set --name $OctopusParameters["webapp-name"] --resource-group $OctopusParameters["resource-group"] --docker-custom-image-name $ImageName --docker-registry-server-url $RegistryUrl 

通过导航到 Docker 提要并搜索 Random Quotes 包,在脚本步骤下添加一个引用包。勾选不获取包引用脚本中的包。点击确定。点击保存保存工艺步骤。

Referenced package

您希望让 GitHub Actions 自动构建 Docker 映像,将其推送到 Docker Hub,创建一个发布,并在 GitHub 代码中部署它。每次提交 main 时都会进行更新。将 node.yml 更新为以下内容:

name: deploytoazure

on:
  push:
    branches: [ master ]
jobs:

  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Install Octopus CLI
        uses: OctopusDeploy/install-octopus-cli-action@v1.1.1
        with:
          version: latest

      - name: Check Out Repo 
        uses: actions/checkout@v2

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v1

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          context: ./
          file: ./Dockerfile
          push: true
          tags: ${{ secrets.DOCKER_HUB_USERNAME }}/randomquotes-js:latest

      - name: sleep
        run: sleep 60

      - name: create Octopus release
        run: octo create-release --project docker --version 0.0.i --server=${{ secrets.OCTOPUS_SERVER }} --apiKey=${{ secrets.OCTOPUS_APIKEY }}

      - name: deploy Octopus release
        run: octo deploy-release --project docker --version=latest --deployto Production --server=${{ secrets.OCTOPUS_SERVER }} --apiKey=${{ secrets.OCTOPUS_APIKEY }} 

这些更改将 Octopus Deploy CLI 安装到机器上,以代表 Octopus Deploy 实例运行命令。在每次推送 Docker 时,脚本会等待 60 秒,然后为 Azure 创建一个新的部署。

提交更改并导航到操作选项卡以确认部署。

GitHub success

导航到 Octopus Deploy 项目,然后发布以查看最新的部署。

Octopus success

前往[webapp-name].azurewebsites.net查看您的网络应用程序:

Random Quotes 2018

进行更改以确认部署已自动更新。在 GitHub 中,编辑文件:

RandomQuotes-JS/source/www/index.html 

Random Quotes year change

将年份改为2021,并将代码提交给 GitHub。提交和推送将触发 GitHub 操作构建。部署完成后,导航到年份发生变化的 web 应用程序。

Random Quotes 2021

结论

GitHub Actions 是一个 CI/CD 平台,增强了所有 GitHub 项目的 CI/CD 功能。GitHub Actions 让开发者可以轻松部署他们的 GitHub 项目。 Octopus Deploy 与 GitHub Actions 协同工作,为管理部署提供专用的连续交付工具。CI/CD 流程可以连接到云目标,比如 Microsoft Azure 或 Amazon Web Services,为应用程序提供一个部署目标。

本教程使用 Octopus Deploy、GitHub Actions、Docker 和 Azure 建立了一个连续交付流程。GitHub 自动检测代码的变化,触发构建,并推送到 Docker。Octopus Deploy 然后创建一个新的版本并部署 Azure Web 应用程序。

试用我们免费的 GitHub Actions 工作流工具,帮助您快速为 GitHub Actions 部署生成可定制的工作流。

有关持续集成(CI)和构建服务器的更多信息,请查看我们的 CI 博客系列。浏览 DevOps 工程师手册了解有关 DevOps 和 CI/CD 的更多信息。

愉快的部署!

将数据库更改部署到 Cassandra - Octopus 部署

原文:https://octopus.com/blog/deploying-to-cassandra

NoSQL 仍然是数据库世界的颠覆者。像 MongoDB、Couchbase、Azure Cosmos DB 和 Amazon DynamoDB 这样的名字是 NoSQL 实现最容易识别的名字。然而,我们越来越多地听到客户谈论 Cassandra。

这篇文章向您展示了如何使用 Octopus Deploy 和 Liquibase 将数据库更改部署到 Cassandra 服务器。

示例项目:Sakila

这篇文章使用了 Sakila 示例项目。Sakila 项目包含使用不同数据库部署技术将 Sakila 数据库部署到不同数据库服务器的示例。

Cassandra 文件夹包含一个 XML 文件,该文件是为使用 Liquibase 产品在 keyspace 中创建表而构建的。

dbchangelog.xml
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog  xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-3.9.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.9.xsd">
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-1">
        <createTable tableName="category">
            <column name="category_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_category_category_id"/>
            </column>
            <column name="name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-2">
        <createTable tableName="language">
            <column  name="language_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_language_language_id"/>
            </column>
            <column name="name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-3">
        <createTable tableName="address">
            <column  name="address_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_address_address_id"/>
            </column>
            <column name="address" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="address2" type="varchar"/>
            <column name="district" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="city_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="postal_code" type="varchar"/>
            <column name="phone" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-4">
        <createTable tableName="film">
            <column  name="film_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_film_film_id"/>
            </column>
            <column name="title" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="description" type="varchar"/>
            <column name="release_year" type="int"/>
            <column name="language_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="original_language_id" type="int"/>
            <column name="rental_duration" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="rental_rate" type="decimal">
                <constraints nullable="false"/>
            </column>
            <column name="length" type="int"/>
            <column name="replacement_cost" type="decimal">
                <constraints nullable="false"/>
            </column>
            <column name="rating" type="varchar"/>
            <column name="special_features" type="varchar"/>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-5">
        <createTable tableName="staff">
            <column  name="staff_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_staff_staff_id"/>
            </column>
            <column name="first_name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="address_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="picture" type="blob"/>
            <column name="email" type="varchar"/>
            <column name="store_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="active" type="boolean">
                <constraints nullable="false"/>
            </column>
            <column name="username" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="password" type="varchar"/>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-6">
        <createTable tableName="store">
            <column  name="store_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_store_store_id"/>
            </column>
            <column name="manager_staff_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="address_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-7">
        <createTable tableName="rental">
            <column  name="rental_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_rental_rental_id"/>
            </column>
            <column name="rental_date" type="date">
                <constraints nullable="false"/>
            </column>
            <column name="inventory_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="customer_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="return_date" type="date"/>
            <column name="staff_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-8">
        <createTable tableName="city">
            <column  name="city_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_city_city_id"/>
            </column>
            <column name="city" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="country_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-9">
        <createTable tableName="film_actor">
            <column name="actor_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_film_actor_actor_id"/>
            </column>
            <column name="film_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_film_actor_actor_id"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-10">
        <createTable tableName="film_category">
            <column name="film_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_film_category_film_id"/>
            </column>
            <column name="category_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_film_category_film_id"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-11">
        <createTable tableName="film_text">
            <column name="film_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_film_text_film_id"/>
            </column>
            <column name="title" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="description" type="varchar"/>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-12">
        <createTable tableName="actor">
            <column  name="actor_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_actor_actor_id"/>
            </column>
            <column name="first_name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-13">
        <createTable tableName="inventory">
            <column  name="inventory_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_inventory_inventory_id"/>
            </column>
            <column name="film_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="store_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-14">
        <createTable tableName="customer">
            <column  name="customer_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_customer_customer_id"/>
            </column>
            <column name="store_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="first_name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_name" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="email" type="varchar"/>
            <column name="address_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="active" type="boolean">
                <constraints nullable="false"/>
            </column>
            <column name="create_date" type="date">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date"/>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-15">
        <createTable tableName="country">
            <column  name="country_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_country_country_id"/>
            </column>
            <column name="country" type="varchar">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="Shawn.Sesna (generated)" id="1603898648791-16">
        <createTable tableName="payment">
            <column  name="payment_id" type="int">
                <constraints nullable="false" primaryKey="true" primaryKeyName="PK_payment_payment_id"/>
            </column>
            <column name="customer_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="staff_id" type="int">
                <constraints nullable="false"/>
            </column>
            <column name="rental_id" type="int"/>
            <column name="amount" type="decimal">
                <constraints nullable="false"/>
            </column>
            <column name="payment_date" type="date">
                <constraints nullable="false"/>
            </column>
            <column name="last_update" type="date"/>
        </createTable>
    </changeSet>
 </databaseChangeLog> 

在本文中,您使用构建服务器或 Octopus 命令行界面(CLI) 将 dbchangelog.xml 文件打包成. zip 包以进行部署。

Cassandra 部署流程

本文假设您知道如何创建 Octopus 项目并向部署过程添加步骤。如果你是 Octopus 的新手,可以考虑阅读我们的入门文档来熟悉这些概念。

在之前的文章中, Liquibase - Apply changeset 模板被用来更新数据库。然而,这个模板只包含了 Liquibase 产品的一小部分功能,已经被替换为 Liquibase - Run 命令

Liquibase - Run 命令更加灵活,已经实现了许多可用于 Liquibase 产品的命令。这个模板还有更多的数据库类型选项,包括雪花卡珊德拉

Cassandra 的部署过程如下所示:

发送开始通知

  • Cassandra -如果数据库不存在,则创建数据库
  • Liquibase -运行更新 SQL 命令
  • 数据库管理员批准
  • Liquibase -运行更新命令
  • 发送成功通知
  • 发送失败通知
  • Deployment process steps for Cassandra in the Octopus UI

发送开始通知

发送开始通知步骤使用Slack-Send Simple Notification社区步骤模板,在 Slack 中发送消息让渠道知道部署已经开始。

挂钩网址:您的 Slack 账户的挂钩链接

  • 通道手柄:要置入的通道
  • 图标网址:发布时使用的图标网址
  • 用户名:发帖用户的姓名
  • 职称:职务职称
  • 消息:帖子详细消息
  • 颜色:颜色为帖
  • 卡桑德拉-创建数据库(如果不存在)

卡桑德拉的数据库被称为键空间Cassandra - Create database 如果不存在的话模板自动在 Cassandra 服务器上创建键空间

服务器名称:Cassandra 服务器的名称或 IP 地址

  • 端口:卡桑德拉端口正在监听
  • (可选)用户名:具有创建密钥空间足够权限的用户名
  • (可选)密码:有足够权限创建密钥空间的用户密码
  • 服务器模式:网络拓扑或简单
  • 键空间:要创建的键空间的名称
  • 复制品数量:要创建的复制品数量
  • Liquibase -运行 updateSQL 命令

Liquibase 中的updateSQL命令分析更改日志,并生成一个包含运行update命令时将执行的 SQL 的文件。该文件随后作为工件上传到章鱼服务器。

专业许可证密钥:某些 Liquibase 命令需要专业许可证密钥。

  • 数据库类型:您要部署到的数据库服务器*的类型。
  • *数据库技术列表并不是利基市场可以部署的完整列表,只是章鱼部署已经过测试。覆盖下拉列表将导致失败,因为模板不知道如何构造 JDBC 连接字符串。

命令:命令下拉列表*。如果命令不存在,可通过点击链( chain icon )图标并输入所需命令来覆盖下拉菜单。

  • *这不是 Liquibase 可用命令的完整列表,只是那些用 Octopus Deploy 测试过的命令。

附加开关 : Liquibase 具有可提供的附加开关,例如设置 loglevel(例如,--logLevel=debug)。

  • 更改日志文件名:这是包含 Liquibase 更改日志的文件。
  • 变更集包:包含变更日志的包。
  • 服务器名称:要连接的服务器的名称或 IP 地址。
  • 服务器端口:服务器监听的端口。
  • 数据库名称:要更新的数据库名称(在 Cassandra 的情况下为 keyspace)。
  • 用户名:用于更新的可选用户名。如果省略用户名和密码,将使用工人/触手的身份。
  • 密码:可选用户名的密码。
  • 连接查询字符串参数:有些数据库服务器需要设置额外的连接参数。在 Cassandra 的例子中,你可以输入;AuthMech=1 如果你想使用用户名/密码。
  • 数据库驱动路径:数据库驱动所在的文件夹。在需要扩展驱动的情况下,使用一个;作为分隔符(Cassandra 需要一个)。如果您使用下载 Liquibase 选项,请留空。
  • 可执行文件路径:liqui base . bat 所在的位置
  • 下载 Liquibase :如果您的软件包中没有包含 Liquibase 产品,使用此选项动态下载 Liquibase、所选Database type的驱动程序和扩展,以及运行 Liquibase 的 Java。
  • Liquibase 版本:默认情况下下载 Liquibase 下载最新版本。使用此选项下载特定版本的 Liquibase。
  • DBA 批准

这一步是可选的,取决于您的 DBA 对自动化部署过程的信心。它暂停您的部署,并允许 DBA 查看由 Liquibase - Run updateSQL 命令步骤生成的 SQL 文件,并批准或拒绝部署。

Liquibase -运行更新命令

这个步骤使用与 Liquibase - Run updateSQL 命令相同的模板,但是使用update命令而不是updateSQL

发送成功通知

该步骤使用与发送开始通知相同的模板,并在部署成功执行时执行。

发送失败通知

这个步骤使用与 Send start notification 相同的模板,仅在部署失败时向 Slack 发送消息。

部署结果

部署完成后,您会看到类似这样的内容:

Deploy Liquibase deployment results in Octopus

使用类似于 TablePlus 的工具,您可以连接到 Cassandra 服务器,并查看用 dbchangelog.xml 文件中的表填充的密钥空间。

keyspace populated with the tables from dbchangelog.xml file

结论

这篇文章演示了如何使用 Octopus Deploy 和 Liquibase 自动部署到 Cassandra 数据库服务器。

愉快的部署!

愉快的部署!

部署到 Google 应用引擎- Octopus Deploy

原文:https://octopus.com/blog/deploying-to-google-app-engine

谷歌应用引擎(GAE)是由谷歌云平台(GCP)提供的最初的平台即服务(PaaS)产品之一。

GAE 托管着用各种不同语言编写的 web 应用程序。它还提供网络路由、作业调度、持久数据存储和任务队列。

在本文中,我将介绍如何将一个示例应用程序部署到 GAE,并操纵网络来实现常见的部署场景,如蓝/绿、金丝雀和特性分支部署。

简单的部署

GAE 为 Java 提供了两种部署:

  • 部署由 GAE 编译的源代码
  • 部署已编译的应用程序

允许 GAE 编译您的源代码是很方便的,尽管在这个例子中我使用了一个已经被我们的 CI 系统编译过的 JAR 文件。

在 GAE,部署已编译应用程序的能力是 Java 独有的。Node、Python、Ruby 和 PHP 等其他运行时通常不会生成编译后的应用程序。Go 是一个明显的例外,在这种情况下,你需要部署你的源代码,让 GAE 为你编译。

我们的示例应用程序是一个简单的 Java Spring web 应用程序,名为 Random Quotes。这个应用程序的源代码可以在 GitHub 中找到。这个应用程序生成一个自包含的 JAR 文件来托管应用程序和一个内置的 web 服务器。

要部署应用程序,您需要在 GCP 项目中创建一个相应的 GAE 应用程序资源。以下步骤显示了通过 web 控制台创建的应用程序资源。第一步是选择托管应用程序资源的位置:

然后定义将托管您的 web 应用程序的环境:

在创建 GAE 实例时,提供了一些关于后续步骤的说明。

该过程的最终结果是创建了下图所示的应用程序:

每个项目只能有一个应用程序资源。如果您尝试创建另一个应用程序,比如在不同的地区,您会看到如下错误:

ERROR: (gcloud.app.create) The project [mattctest] already contains an App Engine application. You can deploy your application using `gcloud app deploy`. 

创建应用程序资源后,您就可以部署 web 应用程序了。一个应用程序资源可以承载许多服务,其中每个服务运行您自己的应用程序。

服务是在一个名为app.yaml的文件中定义的(有点混乱)。下面是一个示例app.yaml文件,您可以使用它来定义和部署您的 Java web 应用程序:

runtime: java11
service: default
instance_class: F2 

运行时是定义将承载您的代码的平台的必需属性。我找不到一个明确的运行时列表,但是javajava8java11都包含在文档和示例的不同地方。我在这里使用java11,因为 Java 11 是 GAE 第二代的一部分。

部署到 GAE 的第一个服务必须被称为default,所以我在service字段中定义了这个名称。

如果您尝试使用非默认名称部署服务,您会得到以下错误:

The first service (module) you upload to a new application must be the 'default' service (module). 

您还需要使用比默认情况下提供的稍微大一点的实例。这是在instance_class属性中定义的。F2 实例提供了 512MB 的内存,这是您的 web 应用程序所需要的。

使用以下命令编译 Java 应用程序:

./mvnw package 

这将在target目录下创建一个 JAR 文件。

在编写本文时,示例应用程序的版本是 0.1.9,所以 JAR 文件被称为target/randomquotes.0.1.9.jar

要部署 web 应用程序,请运行以下命令,替换项目名称以匹配您的环境:

gcloud app deploy .\target\randomquotes.0.1.9.jar --appyaml .\app.yaml --project mattctest 

然后部署编译后的应用程序。部署日志将类似 https://[project name]. UC . r . appspot . com/的 URL 返回到实时服务,您可以在 web 浏览器中打开该服务:

部署功能分支

一种常见的部署模式是让特性分支与主线分支并行部署。为了模拟这种情况,部署 web 应用程序的 blueheader 分支,它会将横幅的背景颜色更改为蓝色。

这个分支的app.yaml文件如下所示:

runtime: java11
service: blueheader
instance_class: F2

env_variables:
  SERVER_SERVLET_CONTEXT_PATH: "/blueheader" 

我给这个服务起了一个新名字,以匹配特性分支的名字。我还定义了SERVER_SERVLET_CONTEXT_PATH环境变量,将其设置为/blueheader。这定义了 web 应用程序期望从中接收流量的上下文路径。这允许您测试一些流量路由规则,这意味着您可以从类似 https://[project name]. UC . r . appspot . com/blue header 的 URL(与 https://blue header-dot-[project name]. UC . r . appspot . com 的唯一服务 URL 相反)访问新服务。

为了将子目录blueheader路由到新服务,创建一个名为displatch.yaml的文件,包含以下内容。这些调度规则定义了如何将流量从 URL 路由到服务:

dispatch:
  - url: "*/"
    service: default

  - url: "*/blueheader"
    service: blueheader

  - url: "*/blueheader/*"
    service: blueheader 

这是通过以下命令部署的:

gcloud app deploy dispatch.yaml --project mattctest 

现在可以在 URL https://[project name]. UC . r . appspot . com/blue header 打开功能分支:

流量分割、金丝雀和蓝/绿部署

现在,让我们看看如何使用流量分流来实施淡黄色和蓝/绿色部署。

为此,您需要将pom.xml文件中的应用程序版本升级到0.1.10:

<?xml version="1.0" encoding="UTF-8"?>
<project  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <version>0.1.10</version>
    ...
</project> 

然后,使用以下命令重新打包该应用程序:

./mvnw package 

使用以下命令部署新版本:

gcloud app deploy .\target\randomquotes.0.1.10.jar --appyaml .\app.yaml --project mattctest --no-promote 

--no-promote选项确保这个新版本不接收任何流量,所以打开 https://[project name]. UC . r . appspot . com/还是会显示之前版本的 web app。

版本选项卡中,有一个按钮叫做分流:

单击此按钮允许您在服务版本之间定向流量。在下面的截图中,你可以看到流量在最新的两个版本之间对半分割。您已经随机分割了流量,因为这允许您刷新 URL 并查看两个版本。但是,如果您正在执行生产 canary 部署,您可能会基于 cookie 或 IP 地址将用户定向到同一版本,这样每个请求就不会被路由到随机版本:

现在对 https://[project name]. UC . r . appspot . com/的请求 50%返回 0.1.9 版本,50%返回 0.1.10 版本。

金丝雀部署是通过逐渐增加新版本服务的流量来实现的。完成任何测试后,蓝/绿部署只需将流量 100%切换到新版本。您可以使用类似 https://[version]-dot-[project name]. UC . r . appspot . com/的 URL 测试任何流量分流规则之外的特定版本。

结论

Google App Engine 为托管 web 应用程序提供了一个灵活的平台。网络路由和流量分流功能允许执行复杂的部署流程,如功能分支、金丝雀和蓝/绿。

在这篇博文中,我部署了一个简单的 Java web 应用程序,并演示了如何执行高级部署模式。

愉快的部署!

部署到谷歌云功能-八达通部署

原文:https://octopus.com/blog/deploying-to-google-cloud-functions

谷歌云功能(GCF)是谷歌的功能即服务(FaaS)平台。它允许简单的应用程序按需运行,以响应通过外部源(如 HTTP 请求)触发的事件,或者来自其他谷歌云平台(GCP)服务触发的事件(如文件上传到 bucket)。

GCF 支持用以下语言编写的函数:

  • 节点. js
  • 计算机编程语言
  • Java 语言(一种计算机语言,尤用于创建网站)
  • 。网络核心
  • 红宝石
  • 服务器端编程语言(Professional Hypertext Preprocessor 的缩写)

Google App Engine (GAE) 一样,GCF 大多期望原始源代码被上传到平台。Java 应用程序有一个例外,它支持 JAR 文件的部署。但是其他编译语言像 Go 和 C#需要上传源代码,GCF 会负责下载依赖项和编译代码。

在本文中,我将演示如何将一个简单的 Java 应用程序部署到 GCF,并探索高级部署模式的选项。

示例应用程序

我们的示例应用程序为随机报价 web 应用程序实现了一个 API。源代码可以在 GitHub 上找到。该 API 唯一的工作是返回一个 JSON blob,其中有一段作者的名言。这个 JSON 的一个例子如下所示:

{
    "quote": "Everything should be made as simple as possible, but not simpler.", 
    "author": "Albert Einstein", 
    "appVersion": "1.0.22", 
    "environmentName": "Google Cloud Functions", 
    "quoteCount": "0" 
} 

JSON 还包括生成响应的函数的版本,以及一个指示该函数实例被调用次数的计数器。计数器允许您跟踪 GCF 是否为您的请求创建了一个新的函数实例,或者重用了一个现有的实例。

这个 API 将由一个简单的 web 应用程序使用。为了方便起见,前端已经发布为 Docker 映像,您可以使用下面的命令运行它,其中Your GCP Project ID将被替换为该函数已经部署到的 GCP 项目的 ID:

docker run \
    -p 8080:8080 \
    -e APIENDPOINT=https://us-central1-[Your GCP Project ID].cloudfunctions.net/my-java-function \
    octopussamples/randomquotesgo 

然后可以在 http://localhost:8080/index . html 打开 web 应用程序。

部署功能

正如简介中提到的,GCF 主要希望您部署函数源代码,而不是编译好的应用程序。这很方便,因为它允许您直接部署 Git 存储库的内容。

GitHub 通过在每次发布时捕获 zip 文件中的代码,使这变得特别容易。这为您提供了强大的工作流程,其中:

  1. Git 存储库标记有给定的版本
  2. GitHub 用相关的 zip 文件创建一个版本
  3. 您将源代码包上传到 GCF

下载并提取最新的版本。然后,您可以使用以下命令部署它:

gcloud functions deploy my-java-function \
    --entry-point com.octopus.RandomQuotes \
    --runtime java11 \
    --trigger-http \
    --allow-unauthenticated 

--entry-point参数定义了响应触发器而运行的类。运行时用--runtime参数定义。您需要使用--trigger-http参数通过 HTTP 来执行这个函数。

最后,您允许匿名用户使用--allow-unauthenticated参数来执行这个函数。

部署该功能后,它会显示在 GCP 控制台中:

高级部署策略

GCP 提供的其他平台,如谷歌应用引擎和谷歌云运行,提供了部署修订或版本的能力,每个版本都有独特的端点。

Google App Engine 具有原生功能,可以在版本之间分割流量,同时提供特定于版本的 URL,如 https://[version]-dot-[project name]. UC . r . appspot . com/允许独立访问各个版本。

Google Cloud Run 允许通过标签部署已命名的修订版,流量可以在标签之间分割,并且可以通过类似 https://[tagname]-randomquotes-5od2layuca-ts.a.run.app/.的 URL 直接访问已标记的修订版

GCF 不提供类似的功能。控制台提供了一个下拉列表,显示功能修订,但是根据我的经验,这个列表只显示失败的部署。在下面的屏幕截图中,您可以看到一个具有多个部署的功能,版本列表仅显示最新版本:

这意味着不能直接与以前版本的函数交互,也不能同时运行两个版本。任何部署策略,如蓝/绿、金丝雀或功能分支,都依赖于部署具有唯一名称的功能,并通过辅助负载平衡服务将流量导向这些功能。

结论

对于任何希望快速部署简单应用程序的人来说,Google Cloud Functions 是一个方便的解决方案,无论是通过 HTTP 支持面向公众的交互,还是对 GCP 生态系统中的事件做出响应。

与 GCP 提供的其他应用托管平台不同,云功能提供了非常简单的版本和网络选项。这意味着高级部署策略必须通过命名约定在外部进行编排。然而,对于更传统的部署,这种缺乏灵活性可能是一种福气,因为功能部署需要很少的努力,尤其是当您考虑到 Google 会为您编译代码时。

愉快的部署!

部署到 Google Cloud Run - Octopus 部署

原文:https://octopus.com/blog/deploying-to-google-cloud-run

Google Cloud Run 是谷歌云平台(GCP)上相对较新的平台即服务(PaaS)产品。它允许您运行和缩放容器图像,同时只为处理请求的时间付费。

在本文中,我将介绍如何在 Cloud Run 中部署一个示例应用程序,并使用流量整形规则来执行部署策略,如 feature branches、canary 和 blue/green。

部署示例应用程序

让我们从部署一个名为随机报价的示例应用程序开始。这个 Java Spring web 应用程序已经被推送到 Docker Hub,我在撰写本文时使用的最新标签是octopussamples/randomquotesjava:0.1.189

当部署到 Cloud Run 时,您需要从将 Docker 映像推送到 Google 容器注册中心(GCR)开始。云运行不支持外部注册表。

使用传统的docker CLI 工具,您可以使用以下命令拉和推映像,确保用包含您的云运行服务的项目 ID 替换cloudrun-314201:

docker pull octopussamples/randomquotesjava:0.1.189
docker tag octopussamples/randomquotesjava:0.1.189 gcr.io/cloudrun-314201/randomquotesjava:0.1.189
docker push gcr.io/cloudrun-314201/randomquotesjava:0.1.189 

其他像skopeo这样的工具复制 Docker 图片更方便。以下命令会将映像从 Docker Hub 注册表直接复制到 GCR:

skopeo copy docker://octopussamples/randomquotesjava:0.1.189 docker://gcr.io/cloudrun-314201/randomquotesjava:0.1.189 

如果您尝试从外部注册表引用 Docker 映像,您会收到以下错误:

ERROR: (gcloud.beta.run.services.replace) Expected a Container Registry image path like [region.]gcr.io/repo-path[:tag and/or @digest] or an Artifact Registry image path like [region-]docker.pkg.dev/repo-path[:tag and/or @digest], but obtained octopussamples/randomquotesjava:0.1.189 

接下来,在名为service.yaml的文件中定义服务 YAML 资源。

如果你熟悉库伯内特,YAML 的结构看起来会很熟悉。它遵循所有 Kubernetes 资源使用的apiVersionkindmetadataspec布局。事实上,您在这里定义的服务是 Knative 的一部分,因为 Cloud Run 是 Knative 服务的托管实现:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: randomquotes
spec:
  template:
    spec:
      containers:
        - image: gcr.io/cloudrun-314201/randomquotesjava:0.1.189
          ports:
            - name: http1
              containerPort: 80 
  • 属性定义了服务的名称。
  • 属性引用了我们复制到 GCR 的 Docker 图像。
  • 可以将spec.template.spec.containers[0].ports.name属性设置为h2c来表示端口由 HTTP2 公开,或者设置为http1来表示端口由 HTTP1 公开。
  • spec.template.spec.containers[0].ports.containerPort属性定义了容器公开的接收 web 流量的端口。

要部署此服务,请运行命令:

gcloud beta run services replace service.yaml --platform managed 

部署服务后,您会收到一个类似于https://randomquotes-5od2layuca-ts.a.run.app的 URL,可以用来访问它。打开 URL 可能会导致显示以下错误:

解决方案是给allUsers用户Cloud Run Invoker权限:

然后,您可以打开您的 web 应用程序:

功能分支部署

要部署从功能分支创建的映像,首先将其复制到 GCR。这里您有一个带有标签0.1.200-blueheader的特征分支图像,您可以使用命令将它复制到 GCR:

skopeo copy docker://octopussamples/randomquotesjava:0.1.200-blueheader docker://gcr.io/cloudrun-314201/randomquotesjava:0.1.200-blueheader 

分配给服务的 URL 基于服务名称。在下面的 YAML 中,您需要更改服务名称以包含要素分支名称:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: randomquotes-blueheader
spec:
  template:
    spec:
      containers:
        - image: gcr.io/cloudrun-314201/randomquotesjava:0.1.200-blueheader
          ports:
            - name: http1
              containerPort: 80 

同样,使用以下命令部署该服务:

gcloud beta run services replace service.yaml --platform managed 

返回的 URL 将类似于https://randomquotes-blueheader-5od2layuca-ts.a.run.app

通过重命名服务,您现在可以将功能分支与主线部署并行运行:

淡黄色和蓝/绿色部署

要执行淡黄色或蓝绿色部署,您可以使用服务修订。修订的名称在spec.template.metadata.name属性中定义。它必须以服务名为前缀,并且只能使用小写字母、数字或破折号。

这里我们定义了一个名为randomquotes-0-1-189的版本:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: randomquotes
spec:
  template:
    metadata:
      name: randomquotes-0-1-189
    spec:
      containers:
        - image: gcr.io/cloudrun-314201/randomquotesjava:0.1.189
          ports:
            - name: http1
              containerPort: 80 

默认情况下,此修订版在部署时将接收 100%的流量。

现在让我们部署一个新版本:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: randomquotes
spec:
  template:
    metadata:
      name: randomquotes-0-1-200-blueheader
    spec:
      containers:
        - image: gcr.io/cloudrun-314201/randomquotesjava:0.1.200-blueheader
          ports:
            - name: http1
              containerPort: 80
  traffic:
  - revisionName: randomquotes-0-1-200-blueheader
    percent: 0
  - revisionName: randomquotes-0-1-189
    percent: 100 

此新版本已被设置为不接收流量,而是将 100%的流量重定向到以前的版本:

【T2

如果您认为以前的版本是蓝绿色部署的蓝色部分,则新版本将是绿色部分。您可以通过为其指定标签来访问此新版本:

这个新版本可以通过一个类似https://green---randomquotes-5od2layuca-ts.a.run.app/的 URL 打开,在将任何主要流量导向它之前进行测试。

金丝雀部署可以通过逐渐将更多流量导向绿色堆栈来实现。以下命令将 10%的流量导向新版本:

gcloud run services update-traffic randomquotes --platform managed --to-revisions=randomquotes-0-1-200-blueheader=10,randomquotes-0-1-189=90 

可以重复此命令,直到 100%的流量都被定向到新版本。在更传统的蓝/绿部署中,在新堆栈上的任何测试完成后,100%的流量会立即切换到新版本。

测试完成后,我们可以用下面的命令删除green标签:

gcloud run services update-traffic randomquotes --platform managed --remove-tags green 

结论

Google Cloud Run 是一个部署网络应用的便捷平台,只有在处理请求时才会产生费用。云运行提供自动扩展来处理传入的请求,流量路由规则允许您实施蓝/绿和金丝雀风格的部署。

这篇文章介绍了如何将 Docker 镜像复制到 GCR,然后部署一个服务。我们操纵修订版和流量规则来实现蓝/绿和金丝雀部署。我们还研究了如何使用标签来访问一个没有流量的版本。

愉快的部署!

如何使用 Octopus Deploy 部署到 MuleSoft 运行时

原文:https://octopus.com/blog/deploying-to-mulesoft-runtime

MuleSoft 开发了独特的软件,允许您创建自定义 API 来从内部或基于云的系统中检索或操作信息。

在这篇文章中,我将向您展示如何使用 Octopus Deploy 将 MuleSoft API 部署到运行 Mule Runtime 的服务器上。

入门指南

要阅读这篇文章,你需要下载以下软件:

安装 Anypoint Studio

Anypoint Studio 可从 MuleSoft 获得,试用期为 30 天。填写完表格后,下载 ZIP 文件并解压到您的磁盘。AnyPoint Studio 并没有“安装”在您的机器上,所以您可以在提取之后执行 AnyPoint Studio。

配置 MuleSoft 运行时

要配置 Mule Community Edition 运行时,首先要安装和配置 Java。MuleSoft 文档推荐 Java 8 ,然而我成功使用了微软 OpenJDK 11

安装 Java 和 Mule 运行时之后,创建以下系统环境变量:

  • JAVA_HOME:设置为你的 Java 文件夹的位置
  • MULE_HOME:将此设置为您的 Mule 运行时文件夹的位置

Environment variables for mule

默认情况下,Mule Community 运行时在前台运行。要将其配置为作为服务运行,请遵循 MuleSoft 文档:

安装 Maven

安装 Maven 是可选的,因为 Anypoint Studio 软件将 Maven 内置于产品中。如果您希望构建服务器构建 Anypoint Studio 项目,那么您的构建服务器需要 Maven 功能。如果 Maven 安装在构建代理上,大多数(如果不是全部的话)现代构建服务器都包含这样的步骤或任务。

安装 Maven 非常简单,只需提取 ZIP 文件,然后将文件夹添加到PATH系统环境变量中。

邮递员测试

创建 API 后,您需要测试它以确保它正常运行。任何能够处理 POST 请求的工具都可以工作,然而,最流行的 API 测试工具是 Postman

Postman 是免费的,可用于 Windows、Mac 和 Linux 操作系统。

创建 API

在这篇文章中,我创建了一个简单的“Hello World”API。顾名思义,调用这个 API 得到的响应是Hello world!

真实世界的 API 更加复杂,但是在本文中,我将向您展示如何使用 Octopus Deploy 部署 API。

创建项目

  1. 在 Anypoint Studio 中,点击文件,然后新建,然后骡子项目

Create new Anypoint project

  1. 给项目命名,点击完成

  1. Mule Palette 中,选择 HTTP 类别,并将监听器拖到画布上。

Add an HTTP listener object

  1. 点击绿色加号按钮配置监听器连接器配置

Create new connector configuration

  1. 配置连接器配置的设置。在这篇文章中,我保留了默认值。

T32

点击测试连接...按钮,以确保此操作有效。

  1. 为 API 定义一个路径。对于这个帖子,我选择了/hello-world

Add a path

  1. Mule PaletteCore 类别中添加一个 Set Payload 对象,并将其拖动到消息流的 Process 部分。

Add a payload object to listener

  1. 点击 fx 按钮使其失效,并为设置静态信息。对于这个帖子,我使用了Hello World!

Define payload return message

  1. 单击绿色的 play 按钮测试您的 API。当控制台窗口显示部署的状态时,您的 API 就准备好了。切换到 Postman(或您选择的工具)来测试您的 API。向http://localhost:8081/hello-world发送帖子请求。

Test local API with postman

创建可部署文件

Anypoint Studio 软件包含内置于产品中的 Maven 版本。这意味着您可以在 Anypoint Studio 本身中创建一个可部署的工件。或者,您可以从构建服务器(或命令行)使用 Maven 来生成可部署文件。

任意点工作室

您可以通过执行以下操作从 Anypoint Studio 中导出您的项目:

  1. 点击文件,然后导出

Export Anypoint project

  1. 选择一个导出向导。对于这篇文章,我选择了 Anypoint Studio 项目来 Mule Deployable Archive(包括 Studio 元数据)。点击下一个的

Choose an export wizard

  1. 选择要导出的项目。这个项目是我唯一创建的项目。点击下一个的

  2. 选择保存文件的位置,点击完成

Finish the wizard

保存操作完成后,它会显示文件的保存位置和名称。在我的例子中,它被保存到C:\Users\Shawn.Sesna\hello-world.jar

Display saved file location

专家

您还可以从构建服务器或命令行使用 Maven 来构建项目。

在本文中,我将向您展示命令行方法,但是构建服务器的方法几乎是相同的。

  1. 导航到项目文件夹。这个文件夹已经包含了一个您在 Maven 中使用的pom.xml文件。

  2. 使用以下命令/目标运行 Maven:

mvn clean package 
  1. 构建完成后,导航到目标子文件夹。构建产生了一个名为hello-world-1.0.0-SNAPSHOT-mule-application.jar的文件。

Successful maven build with generated .jar file

部署 API

如您所见,Anypoint Studio 项目被编译成 JAR 文件。Octopus Deploy 没有部署到 Mule Community Edition 运行时的具体步骤,但是,它包含一个部署 Java 归档步骤。

将 JAR 文件放入内置存储库或外部存储库之后,就可以继续配置部署了。

配置部署 Java 归档文件步骤

这篇文章假设你知道如何在 Octopus Deploy 中创建项目。

要配置 Octopus 部署项目,请使用以下步骤:

  1. 向您的流程添加一个部署 Java 归档步骤。

  2. 选择要部署到的角色。这篇文章使用了角色 Hello-World-API

  3. 选择要部署的包。这篇文章使用了由导出方法hello-world-1.0.0-SNAPSHOT-mule-application.jar创建的包。我重命名了文件hello-world.1.0.0.jar,这样 Octopus 就可以确定包的 SemVer 版本。

  4. Mule Community Edition 运行时希望将 API 部署到特定的文件夹中。在部署 Java 归档步骤的部署部分,勾选使用定制部署目录。输入 Mule Community Edition 运行时应用程序文件夹的位置。

  5. 为部署的 JAR 文件选择最终文件名。如果你不填写这个,Octopus 会创建一个类似下面格式的文件hello-world@S1.0.0@673BE843C229AA40AC1698CC82104BD0。在此部署和运行的同时,下一个版本(如 1.0.1)被部署为hello-world@S1.0.1@673BE843C229AA40AC1698CC82104BD0。Mule 会认为这是一个新的 API,并试图部署它,这与第一个相冲突。如果1.0.11.0.0有相同的文件名,Mule 会发现一个变化,并使用新的 API。

不要勾选净化选项。如果部署了其他 API,这会将它们全部删除。

完成后,它应该看起来像这样:

Configured Deploy Java Archive step

  1. 创建一个版本并部署。

Deployment successfully completed

  1. 使用 Postman 测试 API。

【T2 Use postman to test the deployed API

使用结构化配置变量操作 API

您成功地部署了一个简单的 API,但是,这并不太现实。随着 API 在您的环境中的发展,它更有可能需要更新。

使用结构化配置变量,您可以将显示的消息从Hello World!更改为其他内容。

更新 API 消息

API 消息存储在 JAR 包中的 XML 文件中。通过确定消息的 XPath,可以为部署的 API 更新消息。

<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" 
    xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
    <http:listener-config name="HTTP_Listener_config" doc:name="HTTP Listener config" doc:id="1bcf8768-d2f4-449e-ace0-4a86472cc4e1" >
        <http:listener-connection host="0.0.0.0" port="8081" />
    </http:listener-config>
    <flow name="hello-worldFlow" doc:id="5b6f81da-ecf0-4a71-8299-c9918ed2ca69" >
        <http:listener doc:name="Listener" doc:id="433558cb-1dcc-4385-8126-5b2801575a29" config-ref="HTTP_Listener_config" path="/hello-world"/>
        <set-payload value="Hello World!" doc:name="Set Payload" doc:id="638c8080-540b-4fce-8539-a5e74350ab80" />
    </flow>
</mule> 
  1. 编辑部署 Java 归档步骤。

  2. 点击配置功能按钮。

Click Configure Features button

  1. 启用结构化配置变量功能。

Enable Strucutred Configuration Variables feature

  1. 该消息位于 hello-world.xml 文件中。指定要替换的文件。

Define file to perform replacement

  1. 创建一个 XPath 项目变量并赋予它一个值。在这种情况下,XPath 是/*[local-name()='mule']/*[local-name()='flow']/*[local-name()='set-payload']/@value

  2. 创建一个版本并部署。部署完成后,测试您的 API 以查看新消息。

Use postman to see changed message

结论

在本文中,您学习了如何使用 MuleSoft Anytime Studio 创建 API,以及如何使用 Octopus Deploy 将它部署到运行 Mule Community Edition Runtime 的服务器上。

愉快的部署!

使用管道从 Jenkins 部署到 Octopus-Octopus Deploy

原文:https://octopus.com/blog/deploying-to-octopus-from-jenkins

之前的博客文章中,我向您展示了如何使用构建 Maven 项目并将其发布到 Octopus 所需的工具建立并运行 Jenkins 的基本实例。

在这篇博文中,我们将看看如何使用 Jenkins Pipelines 构建一个Jenkinsfile,它将使用这些工具构建一个 WAR 文件,将其推送到 Octopus,并部署到 Tomcat 服务器。

在 Octopus 中创建 Tomcat 部署项目

第一步是在 Octopus 中创建一个新项目,将 Java web 应用程序部署到 Tomcat。

为此,我们将有一个名为Thymeleaf Demo的 Octopus 项目,只有一个Deploy to Tomcat via Manager步骤。

Deploy to Tomcat step

这一步将把名为demo的包从内置提要部署到位于http://localhost:38080/manager的 Tomcat 实例。

注意,在这一点上,我们可能在内置库中有一个名为demo的包,也可能没有。创建步骤时,我们可以在包可用之前引用它。

Tomcat Manager URL是相对于执行部署的触手而言的。因为触手通常会安装在托管 Tomcat 的机器上,所以 URL 主机名通常是localhost

Tomcat step

保存 API 密钥

我们的Jekninsfile将在使用 Octo CLI 工具时使用 Octopus API 密钥。然而,我们不想将 API 键保存到Jenkinsfile中,因为这个文件应该被签入到源代码控制中。

幸运的是,我们可以在 Jenkins 中保存像 API 密钥这样的秘密信息,并从Jenkinsfile中引用它。

点击凭证➜系统,然后点击全局凭证➜添加凭证。

Jenkins credentials

Kind列表中选择Secret Text,在Secret字段中粘贴 API key,在ID字段中输入OctopusAPIKey

API Key

创建 Jenkinsfile

A Jenkinsfile描述了 Jenkins 将遵循的构建和部署项目的流程。在我们的例子中,Jenkinsfile将描述如何用 Maven 构建一个项目,如何将生成的 WAR 文件推送到 Octopus,然后如何在 Octopus 中创建和部署一个版本。

来看看完整的Jenkinsfile

pipeline {
    agent any
    tools {
        maven 'Maven 3.5.2'
        jdk 'Java 9'
    }
    stages {
        stage ('Initialize') {
            steps {
                sh '''
                    echo "PATH = ${PATH}"
                    echo "M2_HOME = ${M2_HOME}"
                '''
            }
        }

        stage ('Build') {
            steps {
                sh 'mvn package'
            }
        }

        stage ('Deploy to Octopus') {
            steps {
                withCredentials([string(credentialsId: 'OctopusAPIKey', variable: 'APIKey')]) {
                    sh """
                        ${tool('Octo CLI')}/Octo push --package target/demo.0.0.1-SNAPSHOT.war --replace-existing --server https://youroctopusserver --apiKey ${APIKey}
                        ${tool('Octo CLI')}/Octo create-release --project "Thymeleaf Demo" --server https://youroctopusserver --apiKey ${APIKey}
                        ${tool('Octo CLI')}/Octo deploy-release --project "Thymeleaf Demo" --version latest --deployto Integration --server https://youroctopusserver --apiKey ${APIKey}
                    """
                }
            }
        }
    }
} 

首先,我们需要公开我们的构建将使用的工具。在我们的例子中,我们需要使用在之前的博客文章中定义的 Java 和 Maven 工具。

Jenkinsfile中使用的名称需要与我们在 Jenkins 中给工具的名称相匹配。

tools {
    maven 'Maven 3.5.2'
    jdk 'Java 9'
} 

我们可以看到这些工具在Initialize阶段修改了环境变量PATHM2

stage ('Initialize') {
    steps {
        sh '''
            echo "PATH = ${PATH}"
            echo "M2_HOME = ${M2_HOME}"
        '''
    }
} 

执行构建时,这些echo命令在输出中显示以下文本。您可以看到Java 9Maven 3.5.2工具是如何添加到PATH环境变量中的。

[Java_Demo_master-MGS2PX56MOUS3SDGT4NCJIWUFLV7GTCUYZIZ6SVGHQVXJPBCSCFA] Running shell script
+ echo PATH = /var/lib/jenkins/tools/hudson.model.JDK/Java_9/bin:/var/lib/jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.5.2/bin:/var/lib/jenkins/tools/hudson.model.JDK/Java_9/bin:/var/lib/jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.5.2/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin
PATH = /var/lib/jenkins/tools/hudson.model.JDK/Java_9/bin:/var/lib/jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.5.2/bin:/var/lib/jenkins/tools/hudson.model.JDK/Java_9/bin:/var/lib/jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.5.2/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin
+ echo M2_HOME = /var/lib/jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.5.2
M2_HOME = /var/lib/jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.5.2 

Build阶段,我们执行 Maven 构建。

stage ('Build') {
    steps {
        sh 'mvn package'
    }
} 

最后的Deploy to Octopus阶段是我们推送 Maven 构建的 WAR 文件,在 Octopus 中创建一个发布,并将该发布部署到Integration环境中。

stage ('Deploy to Octopus') {
    steps {
        withCredentials([string(credentialsId: 'OctopusAPIKey', variable: 'APIKey')]) {
            sh """
                ${tool('Octo CLI')}/Octo push --package target/demo.0.0.1-SNAPSHOT.war --replace-existing --server https://youroctopusserver --apiKey ${APIKey}
                ${tool('Octo CLI')}/Octo create-release --project "Thymeleaf Demo" --server https://youroctopusserver --apiKey ${APIKey}
                ${tool('Octo CLI')}/Octo deploy-release --project "Thymeleaf Demo" --version latest --deployto Integration --server https://youroctopusserver --apiKey ${APIKey}
            """
        }
    }
} 

注意,我们已经使用withCredentials将保存在 Jenkins 中的 Octopus API 密匙OctopusAPIKey作为变量APIKey公开。我们在这个withCredentials块中运行的任何脚本都可以访问由 Jenkins 维护的秘密。

withCredentials([string(credentialsId: 'OctopusAPIKey', variable: 'APIKey')]) {
    sh """
        ...
    """
} 

withCredentials块中,我们针对 Octo CLI 执行三个命令:pushcreate-releasedeploy-release

如果你还记得之前的博文,那么Octo CLI被定义为一个定制工具。我们可以通过调用${tool('Octo CLI')}/Octo来引用这个工具。

将这个Jenkinsfile与我们的 Java 代码一起检入,我们就可以创建一个 Jenkins 项目来使用它了。

创建詹金斯管道项目

为了使用我们的 Java 项目及其关联的Jenkinsfile,我们需要在 Jenkins 中创建一个新的Multibranch Pipeline项目。

Multibranch pipeline

在这个项目中我们唯一需要改变的设置是Project Repository,我已经将它设置为https://github.com/OctopusDeploy/ThymeleafSpringDemo。这个 repo 持有一个演示的 Spring Java web 应用程序,并且有一个上面描述的Jenkinsfile的副本。

Java Demo

构建和部署

一旦构建完成,这就是我们的Jenkinsfile的结果。

Pipeline Build

在 Octopus 中,WAR 文件被推送到内置库中。

Octopus library

并且已经创建和部署了一个版本。

Octopus Release

然后将应用程序部署到 Tomcat。

Tomcat manager

结论

Jenkins 管道是一种描述构建过程的强大方式,通过使用 Octo CLI 作为定制工具,将 Octopus 集成到您的构建管道中是非常容易的。

如果您对 Java 应用程序的自动化部署感兴趣,下载 Octopus Deploy 的试用版,并查看我们的文档。

使用 Octopus Deploy - Octopus Deploy 部署到 Red Hat OpenShift

原文:https://octopus.com/blog/deploying-to-openshift-with-octopus-deploy

Deploying to Red Hat OpenShift with Octopus Deploy

在之前的一篇文章中,我写了使用 Octopus Deploy 部署到由 Rancher 管理的 Kubernetes (K8s)集群。在这篇文章中,我将讨论一个类似的主题,如何部署到 Red Hat OpenShift。

红帽 OpenShift

和 Rancher 一样,Red Hat OpenShift 也是一个 K8s 管理平台。然而,这是相似性的终点,这两种产品是非常不同的。运行 OpenShift 的系统需求并不是无足轻重的。至少有三个主节点,每个主节点至少有 4 个 vCPUs 和 16 GB RAM。工作节点需要更少的资源:1 个 vCPU 和 8gb RAM。

OpenShift 有一个精简版,设计用于在笔记本电脑上运行,用于开发和试用目的,称为 CodeReady Containers (CRC)。我在这篇文章中使用的是 CRC 版本,因为我的虚拟机管理程序没有足够的剩余资源来托管完整版本的 OpenShift。即使它是一个精简版,CRC 仍然具有相同的整体功能。

使用 CRC

本节分享了我在使用 CRC 时学到的一些经验。如果您不打算使用它,可以跳过这一部分。

要下载 CRC,您需要一个红帽帐户。

CRC 有三种类型:

  • Windows (Hyper-V)
  • macOS (HyperKit)
  • Linux (Libvirt)

在这篇文章中,我使用的是 Windows Hyper-V 版本。

Hyper-V 虚拟交换机

Windows CRC 下载是一个单一的。exe 文件。这个文件负责创建和配置 VM,以便在笔记本电脑上运行 OpenShift。我使用 CRC 发现的一件事是,它专门使用 Hyper-V 中的默认虚拟交换机。一段时间前,Windows 禁用了编辑默认虚拟交换机的功能,因此它永久地停留在使用网络地址转换(NAT)上。然而,我确实发现了 CRC 的一个未被广泛宣传的功能,如果您创建一个名为crc的虚拟交换机,虚拟机的配置将使用该虚拟交换机。

CRC 将 DNS 服务器更改为自身

Red Hat 的人员试图通过一个包罗万象的解决方案来帮助您学习 OpenShift 产品,从而使事情变得尽可能简单。这包括更改您的网络设置,将 DNS 服务器更改为自身,以便您可以解析内置 DNS 条目。我承认,在一切都准备好的时候,我没有对这些消息给予足够的关注,然后我挠头了一会儿,试图找出为什么我的本地 DNS 条目不再工作了。在我弄明白这一点之后,很容易就可以在笔记本电脑本地复制 DNS 条目,这样外部机器就可以与 OpenShift 集群进行交互。

OpenShift 透视图

OpenShift 的接口有两种模式,称为透视图:

显示的选项会根据您使用的透视图而变化。管理员透视图显示了与操作和管理相关的选项,而开发人员透视图只显示了开发人员关心的选项。

创建项目

OpenShift 使用项目来帮助您组织您需要的资源,并将所有东西放在一起。项目由应用程序的所有组件组成,可以从项目屏幕进行监控。要创建项目,请执行以下操作:

  1. 确保您已经选择了管理员视角。
  2. 如果您切换了视角,请等待显示更新(如果您使用 CRC,这可能需要几秒钟)。
  3. 从管理员的角度,点击蓝色的创建项目按钮。
  4. 给你的项目命名,然后点击创建

创建服务帐户

为了部署到 OpenShift,我们需要向 Octopus Deploy 提供它可以用来连接到 OpenShift 的凭证。每个 OpenShift 项目都有一个可以定义服务帐户的部分。创建项目后:

  1. 展开用户管理
  2. 点击服务账户
  3. 点击创建服务账户

创建角色绑定

创建服务帐户后,我们需要给它一个角色,以便它可以在集群上创建资源。

我在使用 UI 获得正确的权限时遇到了一些困难,但是我发现使用命令行oc.exe工具可以让我给我的服务帐户提供正确的权限:

C:\Users\Shawn.Sesna\.kube>oc.exe policy add-role-to-user cluster-admin -z octopusdeploy 

通过运行以下命令确保您在正确的项目中,

C:\Users\Shawn.Sesna\.kube>oc project <project name> 

服务帐户令牌

OpenShift 将自动为您的服务帐户创建一个令牌。该令牌是服务帐户从 Octopus Deploy 向 OpenShift 进行身份验证的方式。要检索令牌的值,请执行以下操作:

  1. 点击服务账户
  2. 点击octopusdeploy(或任何您命名的名称)。
  3. 向下滚动到机密部分。
  4. 点击带有kubernetes.io/service-account-tokentype的条目。

通过单击令牌右侧的复制到剪贴板图标来复制令牌值。

群集 URL

我们需要来自 OpenShift 的最后一条信息是集群的 URL。使用oc.exe命令行工具,我们可以通过status命令快速检索我们需要的 URL:

C:\Users\Shawn.Sesna\.kube>oc.exe status
In project testproject on server https://api.crc.testing:6443 

将 OpenShift 连接到 Octopus 部署

将 OpenShift 连接到 Octopus Deploy 的过程与其他 K8s 集群相同。首先,创建一个帐户,然后添加一个 K8s 集群目标。

创建一个帐户

在我们可以连接我们的 OpenShift K8s 目标之前,我们必须创建一个帐户来验证它。在 Octopus 门户网站中,导航至基础设施选项卡,并点击账户:

  1. 点击添加账户
  2. 选择令牌
  3. 输入账户值,点击保存

现在我们已经创建了一个帐户,我们准备创建我们的 Kubernetes 目标。

添加 K8s 集群

要添加 OpenShift K8s 集群:

  1. 导航至 基础设施➜部署目标
  2. 点击添加部署目标
  3. 点击 KUBERNETES 集群类别。
  4. 然后在 Kubernetes 集群上点击添加

Kubernetes 部署目标表单的两个最重要的部分是:

  • 证明
  • Kubernetes 详细信息

证明

我们将为 OpenShift 集群使用的身份验证类型是 token。

Kubernetes 详细信息

这就是我们使用从oc.exestatus命令中获取的 URL 的地方:https://api.crc.testing:6443。我的集群正在使用自签名证书,因此我选择了跳过 TLS 验证

T31

将“名称空间”框留空。

点击保存就完成了。

通过观察任务选项卡中的初始健康检查来验证连接。

部署到 OpenShift

在这篇文章的开始,我们在 OpenShift 中创建了这个项目。项目名是我们部署到的 K8s 名称空间。这很重要,因为当我们为部署创建步骤时,我们需要确保指定要部署到哪个名称空间。

就像在 Rancher 帖子中一样,我使用了来自《Hello World》之外的帖子中的相同部署过程:构建一个真实世界的 Kubernetes CI/CD 管道帖子。我给Project.Kubernetes.Namespace变量添加了一个值testproject,以将它限定在我们的新目标范围内:

该值将用于我们的部署步骤:

执行部署

我必须对负载平衡器资源的 YAML 做一个小的更改。OpenShift 不喜欢使用外部 IP:

Forbidden: externalIPs have been disabled 

在我注释掉它之后,我能够成功地部署:

在 OpenShift 中,我们可以看到我们所有的资源确实都是被创建的:

结论

在这篇文章中,我演示了如何将 Red Hat OpenShift 与 Octopus Deploy 集成。愉快的部署!

部署到 Payara - Octopus 部署

原文:https://octopus.com/blog/deploying-to-payara

Payara logo on web servers, and Octopus and Payara fish playing in a fish bowl.

当您用 Java 开发应用程序时,您可以选择更多的 web 服务器来部署。Octopus Deploy 内置了对 Tomcat 和 Wildfly (JBoss)的支持,但是也支持其他服务器技术。

在这篇文章中,我演示了如何将 Java 应用程序 PetClinic 部署到 Payara web 服务器上。

基础设施

在这篇文章中,我在 Azure 中设置了一个 MySQL PaaS 服务器作为我的数据库后端,并为 Payara 设置了一个 Ubuntu VM。我用一个 runbook 自动提供这些资源,因此它们可以根据我的需要上下旋转(参见我们的示例实例了解详细信息)。我选择了 Linux 操作系统来运行 web 服务器,但 Payara 也会在 Windows 上运行。

下面是 Azure Resource Manager (ARM)模板和 Bash automation 脚本的代码,用于安装 Octopus 触手和 Payara 服务器:

MySQL PaaS 模板代码

{
    "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "administratorLogin": {
            "type": "string"
        },
        "administratorLoginPassword": {
            "type": "securestring"
        },
        "location": {
            "type": "string"
        },
        "serverName": {
            "type": "string"
        },
        "skuCapacity": {
            "type": "int"
        },
        "skuFamily": {
            "type": "string"
        },
        "skuName": {
            "type": "string"
        },
        "skuSizeMB": {
            "type": "int"
        },
        "skuTier": {
            "type": "string"
        },
        "version": {
            "type": "string"
        },
        "backupRetentionDays": {
            "type": "int"
        },
        "geoRedundantBackup": {
            "type": "string"
        },
        "previewFeature": {
            "type": "string",
            "defaultValue": ""
        },
        "tags": {
            "type": "object",
            "defaultValue": {}
        },
        "storageAutoGrow": {
            "type": "string",
            "defaultValue": "Disabled"
        },
        "infrastructureEncryption": {
            "type": "string",
            "defaultValue": "Disabled"
        }
    },
    "resources": [
        {
            "apiVersion": "2017-12-01-preview",
            "kind": "",
            "location": "[parameters('location')]",
            "name": "[parameters('serverName')]",
            "properties": {
                "version": "[parameters('version')]",
                "administratorLogin": "[parameters('administratorLogin')]",
                "administratorLoginPassword": "[parameters('administratorLoginPassword')]",
                "storageProfile": {
                    "storageMB": "[parameters('skuSizeMB')]",
                    "backupRetentionDays": "[parameters('backupRetentionDays')]",
                    "geoRedundantBackup": "[parameters('geoRedundantBackup')]",
                    "storageAutoGrow": "[parameters('storageAutoGrow')]"
                },
                "previewFeature": "[parameters('previewFeature')]",
                "infrastructureEncryption": "[parameters('infrastructureEncryption')]"
            },
            "sku": {
                "name": "[parameters('skuName')]",
                "tier": "[parameters('skuTier')]",
                "capacity": "[parameters('skuCapacity')]",
                "size": "[parameters('skuSizeMB')]",
                "family": "[parameters('skuFamily')]"
            },
            "tags": "[parameters('tags')]",
            "type": "Microsoft.DBforMySQL/servers"
        }
    ],
    "variables": {}
} 

触手和 Payara 自动化脚本

#!/bin/bash

# Install Octpous listening tentacle
serverUrl="#{Global.Base.Url}"   # The url of your Octous server
thumbprint="#{Global.Server.Thumbprint}"       # The thumbprint of your Octopus Server
apiKey="#{Global.Api.Key}"           # An Octopus Server api key with permission to add machines
name="PetClinic-#{Octopus.Environment.Name}"      # The name of the Tentacle at is will appear in the Octopus portal
publicHostName="#{Global.Environment.Prefix}#{Octopus.Space.Name | Replace " "}.#{Azure.Location.Abbr}.cloudapp.azure.com"      # The url to the tentacle
environment="#{Octopus.Environment.Name}"  # The environment to register the Tentacle in
role="PetClinic-Web"   # The role to assign to the Tentacle
configFilePath="/etc/octopus/default/tentacle-default.config"
applicationPath="/home/Octopus/Applications/"
spaceName="#{Octopus.Space.Name}"

sudo apt install --no-install-recommends gnupg curl ca-certificates apt-transport-https && \
curl -sSfL https://apt.octopus.com/public.key | sudo apt-key add - && \
sudo sh -c "echo deb https://apt.octopus.com/ stable main > /etc/apt/sources.list.d/octopus.com.list" && \
sudo apt update && sudo apt install tentacle -y

sudo /opt/octopus/tentacle/Tentacle create-instance --config "$configFilePath"
sudo /opt/octopus/tentacle/Tentacle new-certificate --if-blank
sudo /opt/octopus/tentacle/Tentacle configure --port 10933 --noListen False --reset-trust --app "$applicationPath"
sudo /opt/octopus/tentacle/Tentacle configure --trust $thumbprint
echo "Registering the Tentacle $name with server $serverUrl in environment $environment with role $role"
sudo /opt/octopus/tentacle/Tentacle register-with --server "$serverUrl" --apiKey "$apiKey" --name "$name" --env "$environment" --role "$role" --space "$spaceName" --publicHostName "$publicHostName"
sudo /opt/octopus/tentacle/Tentacle service --install --start

# Install JDK
sudo apt update
sudo apt install default-jdk -y

# Install Payara
wget --content-disposition 'https://info.payara.fish/cs/c/?cta_guid=b9609f35-f630-492f-b3c0-238fc55f489b&placement_guid=7cca6202-06a3-4c29-aee0-ca58af60528a&portal_id=334594&redirect_url=APefjpGt1aFvHUflpzz7Lec8jDz7CbeIIHZmgORmDSpteTCT2XjiMvjEzeY8yte3kiHi7Ph9mWDB7qUDEr96P0JS8Ev2ZFqahif2huSBfQV6lt4S6YUQpzPMrpHgf_n4VPV62NjKe8vLZBLnYkUALyR2mkrU3vWe7ME9XjHJqYPsHtxkHn-W7bYPFgY2LjEzKIYrdUsCviMgGrUh_LIbLxCESBa0N90vzaWKjK5EwZT021VaPP0jgfgvt0gF2UdtBQGcsTHrAlrb&hsutk=c279766888b67917a591ec4e209cb29a&canon=https%3A%2F%2Fwww.payara.fish%2Fall_downloads&click=5bad781c-f4f5-422d-ba2b-5e0c2bff7098&utm_referrer=https%3A%2F%2Fwww.google.co.za%2F&__hstc=229474563.c279766888b67917a591ec4e209cb29a.1519832301251.1521408251653.1521485598794.4&__hssc=229474563.7.1521485598794&__hsfp=2442083907' --output-document=payara.zip
sudo apt install unzip
sudo unzip payara.zip -d /opt

# Create password files
cat > newpassword.txt <<EOF
AS_ADMIN_PASSWORD=
AS_ADMIN_NEWPASSWORD=#{Payara.Admin.User.Password}
EOF

cat > password.txt <<EOF
AS_ADMIN_PASSWORD=#{Payara.Admin.User.Password}
EOF

# Change admin password
sudo /opt/payara5/bin/asadmin --user admin --passwordfile $PWD/newpassword.txt change-admin-password

# Create service
sudo /opt/payara5/bin/asadmin create-service --name payara

# Start the server (service creation does not start automatically)
sudo /opt/payara5/bin/asadmin start-domain

# Enable remote management
sudo /opt/payara5/bin/asadmin --user admin --passwordfile $PWD/password.txt enable-secure-admin 

Azure MySQL PaaS 故障排除

我在 PaaS MySQL 服务器上遇到了一些问题。它们和我的解决方案一起列在下面,以帮助您避免这些问题。

防火墙

我使用 Azure 向导为 MySQL PaaS 服务器生成 ARM 模板。该向导不包含任何安全组(防火墙)选项,因此在配置服务器时,没有任何东西可以连接到它。使用 Azure 命令行界面(CLI),我打开了防火墙,允许 Octopus Deploy workers 和 Payara VM 与之对话:

az mysql server firewall-rule create --resource-group <your resource group> --server-name '<your server name>' --name AllowAllAzureIps --start-ip-address <start range> --end-ip-address <end range> 

您没有超级权限,并且启用了二进制日志记录

如果您的应用程序的数据库部署包含任何函数或存储过程,您可能会遇到错误You do not have the SUPER privilege and binary logging is enabled。MySQL 的 PaaS 版本不允许您创建这些类型的对象,直到您打开log_bin_trust_function_creators:

az mysql server configuration set --resource-group <your resource group> --server-name "<your server name>" --name log_bin_trust_function_creators --value "ON" 

其他 JDBC 查询字符串参数

在我部署了。首先,有几个额外的 querystring 参数可以让应用程序连接到数据库:

?useSSL=true&serverTimezone=UTC 

用户名

Azure MySQL PaaS 要求用户名也包含主机名,因此用户名如下所示:

username@hostname 

Octopus Deploy

这篇文章假设你熟悉在 Octopus Deploy 中创建项目。

过程

PetClinic 应用程序是一个带有 MySQL 后端的 Spring Boot Java 应用程序。要部署应用程序:

  1. 如果数据库不存在,请创建它。
  2. 使用 Flyway 部署数据库。
  3. 将 PetClinic 应用程序部署到 Payara。

这篇文章是专门关于 Payara 的,所以我们将把重点放在部署到 Payara 上。

部署到帕亚拉

Octopus Deploy 包含用于部署到 Tomcat 和 Wildfly 的特定模板。然而,Payara 包含一个自动部署特性,使得创建 Payara 模板变得没有必要。通过放置。war 文件,Payara 会自动将应用程序部署到服务器上。

添加部署 Java 归档步骤

在您的 Octopus 部署项目中,向您的流程添加一个部署 Java 归档步骤。

配置部署 Java 归档文件步骤

Package Details 下,确保 Deployment 部分已展开(默认情况下应该在新添加的步骤上)。

为了给包重新命名,使其更加用户友好,使用部署包文件名选项指定一个新名称。请注意,文件名会影响应用程序的 URL。

如果部署的包文件名为空,它将使用原始文件名,Payara 服务器上的 URL 将类似于http://PayaraServer/petclinic.web.1.0.21022.194744

我输入了petclinic.war,所以我的网址看起来像http://PayaraServer/petclinic

为了利用 Payara 的自动部署特性,勾选框使用定制部署目录。自动部署文件夹位于域的子文件夹中。如果你已经看过了触手和 Payara 自动化脚本,你会注意到我把 Payara 安装到了/opt/payara5,所以自动部署的完整路径是/opt/payara5/glassfish/domains/domain1/autodeploy

The deploy section of the step template

我还启用了结构化配置变量特性来更新数据库连接信息。为此:

  1. 点击配置功能按钮。
  2. 勾选Structured Configuration Variables框。

Octopus structured configuration variables feature toggle

包含数据库信息的文件是WEB-INF/classes/spring/datasource-config.xml

Location of the structured configuration variables

配置变量

最后,为 datasource-config.xml 文件创建项目变量。我需要更换三个部件:

我使用以下变量:

Octopus variables

变量的Name是 XPath 表达式,它替换了必要的组件。

部署

部署到开发环境,我们可以看到结构化配置变量特性被应用,文件被复制到/opt/payara5/glassfish/domains/domain1/autodeploy

Deployment task log

如果我们导航到 Payara 服务器,我们可以看到 PetClinic 应用程序已经部署,并且正在提取数据。

结论

没有具体的步骤模板,很容易认为 Octopus Deploy 不支持 Payara。我希望这篇文章证明了它确实是受支持的,并且易于部署。

愉快的部署!

部署到 IBM WebSphere Liberty-Octopus Deploy

原文:https://octopus.com/blog/deploying-to-websphere-liberty

An Octopus standing by Websphere servers

在评估软件时,很容易忽略一个选项,因为它没有列出您使用的特定堆栈。然而,仅仅因为它没有被列出,并不意味着它不受支持。

对于基于 Java 的应用程序,Octopus Deploy 包括了部署到 Tomcat 和 Wildfly (JBoss)的具体步骤。这是两种最流行的选择,但是还有更多 web 服务器技术可用。

Web 服务器,比如 Payara 和 IBM WebSphere Liberty,会在应用程序被放在特定的文件夹中时自动部署它。没有必要为这些特定的服务器类型编写步骤模板。

在本文中,我将演示如何将 PetClinic 应用程序部署到 IBM WebSphere Liberty web 服务器上。

IBM WebSphere Liberty

IBM WebSphere Liberty 是一种由 IBM 开发的基于 Java 的应用程序的 web 服务器技术。这个服务器既有付费又有开源变种。当应用程序放在服务器的特定文件夹中时,这两种变体具有相同的自动应用程序部署选项。此外,Liberty 可用于 Windows 和 Linux 操作系统。在这篇文章中,我使用的是 Windows。

建立

要安装 Liberty,只需下载并解压即可。除了安装 Java,这就是设置所需的全部内容。

创建服务器

将服务器软件提取到文件夹后,打开终端并导航到bin文件夹。我的情况是c:\wlp\bin

在那里,运行下面的命令(无论在 Windows 还是 Linux 上,这个命令都是一样的):

server create <Name> 

这将在usr\servers文件夹中创建一个您给它起的名字的文件夹。我给我的宠物诊所打了电话(c:\wlp\usr\servers\petclinic)。

运行以下命令启动服务器:

server start <Name> 

最后,在你的自由服务器上安装章鱼触手。

这篇文章假设你对 Octopus 有一定的了解,但不包括触手安装。

关于如何将 Liberty 设置为作为服务运行,请参考 IBM 文档:

章鱼部署

在这个演示中,我将部署 Java PetClinic 应用程序。这个应用程序使用一个 MySQL 数据库后端,所以我的部署过程将包括创建数据库(如果不存在)、部署数据库更改以及最后将 Java 应用程序部署到 IBM WebSphere Liberty 的步骤。

过程

部署过程包括以下步骤:

  • MySQL:如果数据库不存在,则创建数据库
  • MySQL:如果不存在,则创建用户
  • 参考包中的飞行路线信息
  • DBA 批准(手动干预步骤)
  • 从引用的包进行快速迁移
  • 将 PetClinic 部署到 WebSphere Liberty

这篇文章的重点是部署到 IBM WebSphere Liberty,并将涵盖这一步骤。

部署 Java 归档文件

部署到 IBM WebSphere Liberty 使用 Octopus Deploy 的内置步骤Deploy a Java Archive。编辑您的流程并点击添加步骤并选择部署 Java 归档:

选择您希望部署的角色和包,并滚动到部署部分。选定的包将包含包的版本号。

如果想名字更有用,就填Deployed package file name。在我的例子中,我希望包含环境名,所以我使用了petclinic_#{Octopus.Environment.Name}.war:

如前所述,如果将应用程序放在名为 dropins 的特定文件夹中,IBM WebSphere Liberty 将自动部署该应用程序。 dropins 文件夹直接位于我们之前创建的服务器文件夹之外;c:\wlp\usr\servers\petclinic\dropins

要将我们的文件放在这个特定的文件夹中,我们需要启用使用定制部署目录特性,并指定放在哪里:

PetClinic 应用程序需要用数据库服务器、用户名和密码细节更新的WEB-INF/classes/spring/datasource-config.xml文件。为此,我们将使用结构化配置变量特性。滚动到该步骤的顶部,点击配置功能。启用结构化配置变量功能,点击确定:

滚动到刚刚添加到表单的结构化配置变量部分,输入WEB-INF/classes/spring/datasource-config.xml作为值:

配置此功能后,定义以下项目变量:

  • //*:property[@name='password']/@value:用户账号密码
  • //*:property[@name='username']/@value:MySQL 连接的用户名
  • //*:property[@name='url']/@value : JDBC 到 MySQL 的连接字符串

部署

定义好流程后,我们现在可以将应用程序部署到服务器上了。部署完成后,您应该会看到 PetClinic web 应用程序部署的以下输出:

WebSphere Liberty 的默认端口是9080。导航到http://websphere1:9080/petclinic_Development显示我们的应用程序已经部署:

结论

在这篇文章中,我展示了将基于 Java 的应用程序部署到 IBM WebSphere Liberty web 服务器是多么容易。希望这有帮助。

愉快的部署!

部署流程增强更新- Octopus 部署

原文:https://octopus.com/blog/deployment-process-uservoice-update

我们的 2017 年路线图的重要组成部分之一是我们的用户之声目标:

到 2017 年底:

  • Octopus 将实现超过 200 票的所有用户语音项目

作为这一年的开始,项目建模团队(在一些团队成员的帮助下)实施并发布了以下 3 个 UserVoice 项目,我们希望这些项目能让您的部署过程变得更加简单:

允许步骤被“禁用”或“不活动”

虽然这项功能实际上是在 2016 年 11 月推出的,但我们想在这篇文章中提到它,因为它是客户要求最高的功能之一。

此功能允许您在配置项目部署过程时禁用任何可能导致部署问题的步骤,或者您可能只是想暂时阻止该步骤在部署时运行。以前,您要么必须删除该步骤,将它分配给不做任何事情的角色,要么在部署时跳过它,这不是最干净或最用户友好的解决方案!

现在,我们在该步骤的上下文菜单中添加了一个选项,允许您禁用该步骤,以便在消除新步骤的任何缺陷的同时仍然可以执行部署。

要禁用一个步骤,从步骤上下文菜单中选择Disable选项

New Disable option in step context menu

一旦禁用了某个步骤,它将不会包含在禁用该步骤时创建的任何发布的部署计划中

Disabled step in deployment process

要再次启用禁用的步骤,只需从步骤上下文菜单中选择Enable选项

New Enable option in step context menu when step is disabled

步骤的克隆

这是一个在蓝鳍 Chrome 扩展中可用的功能,但是由于不是所有的组织都允许安装浏览器扩展/插件,或者可能你没有使用 Chrome,我们决定将该功能引入 Octopus,以便我们所有的客户都可以利用该功能。

要克隆一个步骤,您只需从步骤上下文菜单中选择Clone选项

New Clone option in step context menu

这将复制您想要克隆的步骤,并将其添加到正在克隆的步骤下面

Cloned step added to deployment process

允许步骤的运行条件基于变量

该特性允许您在运行时定制您的部署流程,为您提供了基于 Octopus 变量表达式布尔结果有条件运行跳过动作的选项。

New Variable based Run Condition option

一旦您选择了Variable: only run when then variable expression is true选项,您将会得到一个额外的字段,您可以在其中输入Octopus Variable Expression

New field for entering the variable expression to evaluate

根据变量的真值运行步骤:

#{VariableToCheck}(如果要检查任何不是undefined、空、0False的值)或#{if VariableToCheck == "ValueToCheckAgainst"}true{/if}(如果要检查特定值)

基于变量的 falsy 值运行步骤:

#{if VariableToCheck != "ValueToCheckAgainst"}true{/if}

仅在所有先前步骤成功时运行一个步骤,并基于变量的真值:

#{unless Octopus.Deployment.Error}#{VariableToCheck}#{/unless}

如果前面的任何步骤失败,运行一个步骤,并基于变量的真值:

#{if Octopus.Deployment.Error}#{VariableToCheck}#{/if}

最后

我们今年有了一个良好的开端,我们前面还有一长串其他出色的增值功能。我们真的很兴奋,并期待着解决这些高度要求的功能。

我们真的很感谢我们所有的客户输入-所以请确保您添加您的投票到您最喜爱的功能请求!

Octopus 3.0 (RFC)中的部署目标- Octopus 部署

原文:https://octopus.com/blog/deployment-targets-in-octopus-3

在这篇文章中,我想讨论一下我们对触手所做的一些改变,以及作为 Octopus 3.0 一部分的总体部署目标。

概要:

  • 多种类型的“机器”(监听和轮询触角、SSH、Azure 网站、Azure 云服务、WebDeploy、离线 drops)
  • 像贝壳一样的触手
  • 开源触手
  • 不再支持 FTP 部署

多种类型的机器(部署目标)

Octopus 和触手是一起设计的,并且假设 Octopus 将部署到的唯一类型的“目标”将总是运行触手。2013 年,我们增加了对 Azure 云服务和 FTP 站点的支持,两者都是为了处理无法安装触手的情况。这些没有出现在 environments 页面上,没有参与运行状况检查,并且不可能并行部署到多个环境中(例如,上传到多个 FTP 存储库)。

我们最常见的一些功能需求是添加对新类型部署目标的支持:特别是对部署到 Linux 服务器的支持,对部署到 Azure 网站的更好支持,以及通过创建可以手动执行的包来部署到“离线”机器的能力。

为此,我们将把 Octopus 中的“机器”概念扩展到更一般的“部署目标”概念。在 environments 页面中,您可以添加不同的部署目标,而不是添加机器(这总是意味着触手可及):

New deployment target dialog for Octopus 3.0

以前,为了让 FTP 和 Azure 云服务步骤工作,你必须有一个特殊的触手在 Octopus 服务器上运行。对于 3.0,这将不再是必需的;Octopus 将能够原生推送 Azure 网站/云服务。

如上图所示,我们添加了一些新的部署目标。 SSH 目标允许您使用用户名/密码认证或证书部署到任何运行 SSH 的服务器。这些可能是 Amazon AWS Linux 实例、您自己的本地 Linux 服务器、运行 OSX 的机器,甚至是 Raspberry Pi。我们将执行 bash 脚本,而不是运行 PowerShell 脚本。你可以在我们的 RFC 中读到更多关于 Linux/SSH 计划的信息。

WebDeploy 目标将允许您部署到任何启用了 WebDeploy 的服务器。一些第三方主机供应商,以及微软 Azure,都支持 WebDeploy。一个特定的 Azure 网站部署目标使用 WebDeploy 客户端,但更容易设置(一旦你将 Octopus 连接到你的 Azure 订阅,你就可以从列表中选择网站)。

离线放置是一个特殊的部署目标,它将捆绑执行部署所需的所有文件,并将它们放在一个文件共享上,准备好复制到 USB 驱动器并手动部署。

就像触角一样,这些机器类型可以用角色来标记,您将能够并行地部署到它们。例如,如果你在不同的地区有两个 Azure 网站,你可以给他们相同的角色,然后将该角色设置为 NuGet 包步骤的目标——然后包将被并行推送到两个站点。

根据目标的不同,部署逻辑的执行会略有不同。对于 Tentacles 和 SSH 部署,我们将在远程目标机器上执行配置转换和运行脚本。对于 Azure 网站、云服务和 WebDeploy,它们将在 Octopus 服务器上执行,然后上传。对于离线放置,它们根本不会被执行,相反,执行它们所需的代码将包含在捆绑包中。

展望未来,我们计划开发更多的目标类型,特别是 PowerShell 远程目标。这些只是章鱼 3.0 的一部分。

像贝壳一样的触手

我们正在做的另一个大的改变是触手目前的设计。目前,触手是一个与章鱼沟通的代理。它知道如何做很多事情:

Tentacle currently

举个例子,组成触手的 C#代码知道如何进行配置转换、修改 IIS 和其他一些事情。如果我们需要改变这些功能的工作方式,我们必须发布新版本的触手。

当我们添加 SSH 支持时,我们意识到同样的架构无法工作——SSH 所能做的就是运行命令和移动文件。配置转换的所有逻辑和约定,等等。需要从 Octopus 服务器上推过来。

将通信逻辑(事物的 SSH 方面)从部署逻辑中分离出来是一个更好的架构。因此,我们决定在 3.0 中对触须做同样的事情:触须将只知道如何通过安全连接运行命令或传输文件。所有用于部署的逻辑和工具都将从 Octopus 服务器上发送,这意味着触手升级将远不那么常见。

Tentacle in future

从 3.0 开始,除了支持轮询和监听模式之外,可以把触手看作 SSH 的面向 Windows 的版本。它只知道如何执行 PowerShell 脚本或传输文件。其他的东西——配置转换的东西,IIS 的东西——将是 PowerShell 脚本,Octopus 将确保在部署执行之前存在于 Octopus 服务器上。

开源触手的核心包部署组件

现在,我们将通信通道从部署引擎中分离出来,我们获得了一个新的可能性:在部署过程中,所有由触手执行的脚本和部署逻辑(以及 SSH 部署中使用的脚本和工具)现在都可以开源。我们的计划是将其作为 GitHub 项目来管理。如果你不喜欢我们在部署过程中调用的脚本,你可以派生它,构建你自己的触手包,把它放在 Octopus 服务器上,在部署过程中使用你自己的脚本/工具。这也意味着我们可以接受社区的贡献,这对支持各种 Linux 发行版特别有帮助。

停止支持 FTP

查看我们的使用统计数据,FTP 是最不常用的部署目标(世界上约 253 个 FTP 步骤,相比之下,36,000 个 NuGet 包步骤),我怀疑其中大多数是针对 Azure 网站的。

由于我们正在添加原生 WebDeploy 和 Azure 网站支持,很难证明继续将 FTP 作为独立的步骤或机器类型来维护是合理的。出于这个原因,我们会反对它。我们将制作一个使用命令行 FTP 工具的步骤模板,该工具可以作为替代工具。

结论

我们仍然计划在三月底/四月初发布 Octopus 3.0 的预览版,即使到那时所有的部署目标还没有完全完成。同时,我们非常感谢您对我们上述计划的评论,尤其是对开源核心部署脚本/工具的评论。请在下面的评论中告诉我们!

对添加部署目标体验和界面- Octopus Deploy 的改进

原文:https://octopus.com/blog/deployment-targets-tweaks

Illustration of an idea turning into the new deployment target UX

我们希望 Octopus 对新用户和现有用户都易于使用,但是当我们查看遥测数据时,我们看到新用户在他们的入职会议期间创建了许多环境,而没有多少部署目标。今年年初,我们围绕 onboarding 体验进行了一些可用性测试,以确定我们的用户遇到的任何痛点,作为这些测试的结果,我们最近改变了用户体验,以添加新的部署目标。让我们看看我们发现的问题,以及我们如何试图解决它们。

单选按钮太多

我们最近引入了不少新的部署目标类型。用户总是单击单选按钮来选择他们正在添加的部署目标的类型,但是单选按钮不能很好地适应大量的选择。感觉选项的数量达到了一个临界点,因此需要一些爱。

解决方案

我们本可以将单选按钮转换成卡片,然后就此不管了,但是,这并没有解决根本问题,而且仍然会相当令人沮丧。首先,我们决定将部署目标选择移动到一个单独的屏幕上,以使选择更加集中。这也为我们提供了一个对部署目标进行分类的机会,以便更好地反映我们的文档和您,即用户,在来到这个屏幕时的想法。

术语

可用性研究清楚地表明,我们没有做足够的工作来解释我们的术语或困难的概念,我们需要做更多的帮助。部署目标屏幕就是这样一个屏幕。我们有许多客户陷入困境,并询问轮询触角和倾听触角之间的区别。对于不知道触手是什么的新客户来说,就更困惑了。直到用户安装了触手,才看到这个解释触手是什么以及两种类型区别的图片。

解决方案

因为触手安装程序上的图像非常有用,作为卡设计的一部分,我们添加了这些图像和描述。

死胡同

在可用性测试期间,我们看到用户在添加轮询触手时,在 UI 中遇到了一个死胡同。我们没有告诉你轮询触角会自动添加到部署目标列表中。

解决方案

我们现在清楚地表明,安装轮询触手后,它将出现在部署目标列表中,并且我们添加了一个返回部署目标列表的链接。

其他改进

我们还做了许多其他的小改动,以改进不同部署目标屏幕之间的导航,并使事情更容易找到。这包括在可能的地方添加面包屑,突出触手下载按钮。

结论

图表,描述和及时的链接摇滚!可以说,我们对此感到兴奋,并希望它能让添加部署目标成为一种更愉快的体验。下载最新版本,看看新的用户界面和 UX,让我们知道你的想法。

弃用身份验证扩展- Octopus 部署

原文:https://octopus.com/blog/deprecating-authentication-extensions

Octopus 服务器不支持 BYO 认证机制。

在这篇文章中,我将解释为什么会发生这种情况,如果您使用的是定制的身份验证提供者,当发生变化时,您需要做些什么。

Octopus 服务器扩展性

在 Octopus 3.5 中,我们引入了服务器可扩展性的概念。这主要是由客户需求驱动的,允许我们的客户根据他们的独特要求调整八达通的认证机制。

当时,为定制开放认证管道是双赢的;我们的客户可以确保系统按照他们组织的要求精确地工作,并且我们可以减轻一些支持所有需要的不同机制的压力。

我们还认为这是一个机会,可以重新设计产品中包含的一些核心依赖项,以简化开发过程。通过分解 Octopus 服务器的职责,我们希望将系统的各个部分相互分离,从而加快开发周期。

从那以后,Octopus Server 有了显著的增长,随着我们的增长,我们以前的一些假设没有产生预期的影响,反而导致了不必要的副作用。在尝试将 Octopus 依赖项隔离到单独的库以进行开源认证时,如果不为我们的客户提供方便,跨依赖项的开发会变得更加困难。

问题

当一个扩展库的契约可供公众使用时,您就承诺维护和继续该契约的功能。这意味着早期做出的架构决策,在当时可能是正确的,变得难以适应,特别是当新功能需要对底层系统进行更改时。

由于库依赖层次结构的构造方式,对 Octopus 服务器使用的基本库的更改需要这些库的用户重新构建他们的定制以继续使用它们。这给本应简单的服务器升级过程增加了不必要的、常常令人沮丧的步骤。

让核心抽象跨库和存储库分布也会影响开发——当您想要进行横切变更时,当代码不在同一个存储库中时,它们更难推理和执行。

尽管让认证提供者开放供审查是一个合理的举措(并且我们继续通过我们的 GitHub 库为各种库这样做),他们开放供扩展的方式不再符合我们希望 Octopus Server 进入的技术方向。因此,我们决定重新考虑我们当前的服务器可扩展性模型。

有什么变化

如果您还没有为 Octopus 服务器实例构建和安装定制的身份验证扩展,那么这些更改不会影响您。您现在可以停止阅读并继续升级,无需任何更改。

在短期内,自定义扩展将像过去一样工作。您需要重新编译您的库来考虑依赖关系的变化,但是将它们加载到 Octopus 服务器实例的机制将保持不变。然而,在 2023 年底 Octopus Server 的未来版本中,所有自定义认证机制都将停止工作。

这篇文章的早期版本指出,需要在 API 调用中设置环境变量和更改身份验证。该产品的最新更新意味着不再需要这些变通办法。

侧面影响-自定义路线处理程序

支持身份验证可扩展性的一个副作用是能够注入自定义 HTTP 处理程序来响应 API 请求。随着这些最初的改变,这种未记录的能力将不会受到影响,但是当扩展被完全废弃时,这种能力也将被移除。

接下来会发生什么

为了改善我们的交付流程并加强对身份验证提供商的安全检查,我们计划在 2023 年底之前全面取消 Octopus 部署中的 BYO 身份验证功能。

从现在到 2023 年底,我们将继续尽可能地支持扩展,并审查我们如何将客户创建的自定义更改整合到官方提供商本身。

根据目前的反馈,我们预计大多数定制只是对现有提供商的改进,但是我们将调查完全定制集成对用户的影响。如果你也包括在内,请在下面的评论中告诉我们。

如果变化对你有影响,请联系我们

你们中的一些人可能已经实现了从我们的某个库派生出来的身份验证机制,一些人可能已经开发了一个完全自定义的身份验证提供程序。如果您无法迁移到内置机制之一,我们希望更好地了解您的需求,以便我们能够解决缺失的功能。

通过 support@octopus.com与我们联系,或者在下面评论,让我们知道这是否会影响你的八达通实例。

结论

我们不会轻视章鱼的反对意见。在我们软件的悠久历史中,我们对强大的向后兼容性感到自豪。然而,有时我们需要重新考虑那些不再符合我们客户或 Octopus 服务器最佳利益的支持功能。

随着我们逐渐远离身份验证可扩展性模型,我们希望利用接下来的 12 个月来减少这可能对当前依赖它的任何人造成的影响。

愉快的部署!

正在删除触手章鱼部署的 Azure VM 扩展

原文:https://octopus.com/blog/deprecating-azure-vm-extension

触手的 Azure VM 扩展在 2021 年被弃用,但我们没有将其从市场上移除。它不再与新版本的触手完全兼容,所以我们计划在 2023 年 3 月下旬将其移除。

在这篇文章中,如果你的工作流程受到影响,我会带你走一遍替代方案。

VM 扩展仅适用于 Azure 中的 Windows VMs。

为什么我们要移除触手的 Azure VM 扩展

在最近一篇关于触手的文章中。NET 版本变化,我们解释说我们不再支持旧版本。触须中的 NET 版本。这意味着 Windows 虚拟机没有。NET 4.8 运行时不再运行最新版本的触手。

2022 年,Azure VM 扩展仍然部署了几千个 Windows VMs。几年前,当 Azure 转向 ARM 模板和脚本扩展时,我们就反对这个扩展。因此,当部署到具有较旧操作系统版本的虚拟机时,该扩展不再有效。

我们还将在 2023 年 3 月下旬从市场上移除该扩展,因为它已经超过了折旧期。

未来如何调配触手虚拟机

我们建议从现在开始使用 ARM 模板PowerShell DSC 扩展来部署触角。

下面是实现这一点的基本步骤。

触手版&。网络版

无论您的供应方法是什么,从 6.3 开始的触手都需要。NET 4.8 运行时在 Windows 上运行。我们建议操作系统版本。默认安装. NET 4.8。如果没有,请使用 ARM 模板或 DSC 配置在您的虚拟机上安装。

或者,您可以将触手版本锁定为 6.2,这与大多数旧版本兼容。NET framework 版本。锁定版本后,您可以使用 DSC 配置来安装 6.2 触手并忽略较新的版本。下一节显示了一个 DSC 配置示例。

准备 DSC 扩展

Octopus Deploy 提供了一个 DSC 模块,你可以用它来展开触角。正如在我们的文档中解释的,第一步是创建一个包含 DSC 源文件和 DSC 配置的 ZIP 文件。配置文件可以保持简单,或者您可以修改它以接受工作流的参数。比如这里我们增加一个CommunicationMode参数,以不同的模式部署触角。

configuration OctopusTentacle
{
    param ($ApiKey, $OctopusServerUrl, $Environments, $Roles, $ServerPort, $CommunicationMode)

    Import-DscResource -Module OctopusDSC

    Node "localhost"
    {
        cTentacleAgent OctopusTentacle
        {
            Ensure = "Present"
            State = "Started"

            # Tentacle instance name. Leave it as 'Tentacle' unless you have more
            # than one instance
            Name = "Tentacle"

            # Registration - all parameters required
            ApiKey = $ApiKey
            OctopusServerUrl = $OctopusServerUrl
            Environments = $Environments
            Roles = $Roles

            # How Tentacle will communicate with the server
            CommunicationMode = $CommunicationMode
            ServerPort = $ServerPort

            # Where deployed applications will be installed by Octopus
            DefaultApplicationDirectory = "C:\Applications"

            # Where Octopus should store its working files, logs, packages etc
            TentacleHomeDirectory = "C:\Octopus"
        }
    }
} 

如果您将触手锁定到 6.2 版本,请更新 DSC 配置,以便在安装期间只下载 6.2 版本。

configuration OctopusTentacle
{
    param ($ApiKey, $OctopusServerUrl, $Environments, $Roles, $ServerPort, $CommunicationMode)

    Import-DscResource -Module OctopusDSC

    Node "localhost"
    {
        cTentacleAgent OctopusTentacle
        {
            Ensure = "Present"
            State = "Started"

            # Tentacle instance name. Leave it as 'Tentacle' unless you have more
            # than one instance
            Name = "Tentacle"

            # Registration - all parameters required
            ApiKey = $ApiKey
            OctopusServerUrl = $OctopusServerUrl
            Environments = $Environments
            Roles = $Roles

            # How Tentacle will communicate with the server
            CommunicationMode = $CommunicationMode
            ServerPort = $ServerPort

            # Where deployed applications will be installed by Octopus
            DefaultApplicationDirectory = "C:\Applications"

            # Where Octopus should store its working files, logs, packages etc
            TentacleHomeDirectory = "C:\Octopus"

            # Lock Tentacle to 6.2 to support older runtimes
            tentacleDownloadUrl = "https://download.octopusdeploy.com/octopus/Octopus.Tentacle.6.2.277.msi"
            tentacleDownloadUrl64 = "https://download.octopusdeploy.com/octopus/Octopus.Tentacle.6.2.277-x64.msi"
        }
    }
} 

准备手臂模板

DSC 扩展的位置

DSC 扩展需要部署到与 VM 相同的位置才能找到它。您可以对 VM 和扩展的位置使用相同的表达式,比如[resourceGroup().location],或者使用一个参数,比如[parameters('vmLocation')]

将位置定义为参数时,该值必须是区域的 ID,例如australiacentralwestus2

要查看所有区域及其 id 的列表,使用命令az account list-locations -o table

如果它是一个现有的虚拟机,您也可以通过转到虚拟机页面并查看 JSON 视图来找到它。

使用 ARM 模板配置触手虚拟机

最简单的选择是将触手与 VM 和其他相关资源一起部署。一切都是同时定义和部署的,如果需要,可以很容易地重新部署。

下面是 ARM 模板可能的样子。

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "vmAdminUsername": {
      "type": "string",
      "metadata": {
        "description": "Admin username for the Virtual Machine."
      }
    },
    "vmAdminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "Admin password for the Virtual Machine."
      }
    },
    "vmDnsName": {
      "type": "string",
      "metadata": {
        "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
      }
    },
    "vmSize": {
      "defaultValue": "Standard_D2_v2",
      "type": "string",
      "metadata": {
        "description": "Size of the Virtual Machine"
      }
    },
    "tentacleOctopusServerUrl": {
      "type": "string",
      "metadata": {
        "description": "The URL of the octopus server with which to register"
      }
    },
    "tentacleApiKey": {
      "type": "securestring",
      "metadata": {
        "description": "The Api Key to use to register the Tentacle with the server"
      }
    },
    "tentacleCommunicationMode": {
      "defaultValue": "Listen",
      "allowedValues": [
        "Listen",
        "Poll"
      ],
      "type": "string",
      "metadata": {
        "description": "The type of Tentacle - whether the Tentacle listens for requests from server, or actively polls the server for requests"
      }
    },
    "tentaclePort": {
      "defaultValue": 10933,
      "minValue": 0,
      "maxValue": 65535,
      "type": "int",
      "metadata": {
        "description": "The port on which the Tentacle should listen, when CommunicationMode is set to Listen, or the port on which to poll the server, when CommunicationMode is set to Poll. By default, Tentacle's listen on 10933 and poll the Octopus Server on 10943."
      }
    },
    "tentacleRoles": {
      "type": "string",
      "metadata": {
        "description": "A comma delimited list of roles to apply to the Tentacle"
      }
    },
    "tentacleEnvironments": {
      "type": "string",
      "metadata": {
        "description": "A comma delimited list of environments in which the Tentacle should be placed"
      }
    }
  },
  "variables": {
    "namespace": "octopus",
    "location": "[resourceGroup().location]",
    "tags": {
      "vendor": "Octopus Deploy",
      "description": "Example deployment of Octopus Tentacle to a Windows Server."
    },
    "diagnostics": {
      "storageAccount": {
        "name": "[concat('diagnostics', uniquestring(resourceGroup().id))]"
      }
    },
    "networkSecurityGroupName": "[concat(variables('namespace'), '-nsg')]",
    "publicIPAddressName": "[concat(variables('namespace'), '-publicip')]",
    "vnet": {
      "name": "[concat(variables('namespace'), '-vnet')]",
      "addressPrefix": "10.0.0.0/16",
      "subnet": {
        "name": "[concat(variables('namespace'), '-subnet')]",
        "addressPrefix": "10.0.0.0/24"
      }
    },
    "nic": {
      "name": "[concat(variables('namespace'), '-nic')]",
      "ipConfigName": "[concat(variables('namespace'), '-ipconfig')]"
    },
    "vmName": "[concat(variables('namespace'),'-vm')]"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-04-01",
      "name": "[variables('diagnostics').storageAccount.name]",
      "location": "[variables('location')]",
      "tags": {
        "vendor": "[variables('tags').vendor]",
        "description": "[variables('tags').description]"
      },
      "kind": "Storage",
      "sku": {
        "name": "Standard_LRS"
      },
      "properties": {}
    },
    {
      "type": "Microsoft.Network/networkSecurityGroups",
      "apiVersion": "2021-02-01",
      "name": "[variables('networkSecurityGroupName')]",
      "location": "[variables('location')]",
      "tags": {
        "vendor": "[variables('tags').vendor]",
        "description": "[variables('tags').description]"
      },
      "properties": {
        "securityRules": [
          {
            "name": "allow_listening_tentacle",
            "properties": {
              "description": "Allow inbound Tentacle connection",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "10933",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 123,
              "direction": "Inbound"
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[variables('publicIPAddressName')]",
      "apiVersion": "2021-02-01",
      "location": "[variables('location')]",
      "tags": {
        "vendor": "[variables('tags').vendor]",
        "description": "[variables('tags').description]"
      },
      "properties": {
        "publicIPAllocationMethod": "Dynamic",
        "dnsSettings": {
          "domainNameLabel": "[parameters('vmDnsName')]"
        }
      }
    },
    {
      "type": "Microsoft.Network/virtualNetworks",
      "name": "[variables('vnet').name]",
      "apiVersion": "2021-02-01",
      "location": "[variables('location')]",
      "tags": {
        "vendor": "[variables('tags').vendor]",
        "description": "[variables('tags').description]"
      },
      "dependsOn": [
        "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]"
      ],
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('vnet').addressPrefix]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('vnet').subnet.name]",
            "properties": {
              "addressPrefix": "[variables('vnet').subnet.addressPrefix]",
              "networkSecurityGroup": {
                "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
              }
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[variables('nic').name]",
      "apiVersion": "2021-02-01",
      "location": "[variables('location')]",
      "tags": {
        "vendor": "[variables('tags').vendor]",
        "description": "[variables('tags').description]"
      },
      "dependsOn": [
        "[concat('Microsoft.Network/virtualNetworks/', variables('vnet').name)]",
        "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
        "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "[variables('nic').ipConfigName]",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
              },
              "subnet": {
                "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('vnet').name), '/subnets/', variables('vnet').subnet.name)]"
              }
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[variables('vmName')]",
      "apiVersion": "2021-04-01",
      "location": "[variables('location')]",
      "tags": {
        "vendor": "[variables('tags').vendor]",
        "description": "[variables('tags').description]"
      },
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', variables('diagnostics').storageAccount.name)]",
        "[concat('Microsoft.Network/networkInterfaces/', variables('nic').name)]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computerName": "[variables('vmName')]",
          "adminUsername": "[parameters('vmAdminUsername')]",
          "adminPassword": "[parameters('vmAdminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "MicrosoftWindowsServer",
            "offer": "WindowsServer",
            "sku": "2016-Datacenter",
            "version": "latest"
          },
          "osDisk": {
            "createOption": "FromImage",
            "caching": "ReadWrite",
            "managedDisk": {
              "storageAccountType": "Standard_LRS"
            }
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nic').name)]"
            }
          ]
        },
        "diagnosticsProfile": {
          "bootDiagnostics": {
            "enabled": true,
            "storageUri": "[concat('http://', variables('diagnostics').storageAccount.name, '.blob.core.windows.net')]"
          }
        }
      }
    },
    {
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "name": "[concat(variables('vmName'),'/dscExtension')]",
      "apiVersion": "2021-04-01",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]"
      ],
      "properties": {
        "publisher": "Microsoft.Powershell",
        "type": "DSC",
        "typeHandlerVersion": "2.77",
        "autoUpgradeMinorVersion": true,
        "forceUpdateTag": "2",
        "settings": {
          "configuration": {
              "url": "https://url-to-storage/OctopusTentacle.zip",
              "script": "OctopusTentacle.ps1",
              "function": "OctopusTentacle"
          },
          "configurationArguments": {
              "ApiKey": "[parameters('tentacleApiKey')]",
              "OctopusServerUrl": "[parameters('tentacleOctopusServerUrl')]",
              "Environments": "[parameters('tentacleEnvironments')]",
              "Roles": "[parameters('tentacleRoles')]",
              "ServerPort": "[parameters('tentaclePort')]",
              "CommunicationMode":"[parameters('tentacleCommunicationMode')]"
          }
        },
        "protectedSettings": null
      }
    }
  ]
} 

在本例中,网络组安全规则允许通过默认端口 10933 与侦听触角对话。轮询触角不需要开放端口,也可以在没有端口的情况下部署。

有关更多示例,请参见我们关于通过 DSC 在 ARM 模板中安装触手的文档。

使用 ARM 模板在现有虚拟机上安装触手

还可以使用 ARM 模板将触手部署到现有的虚拟机上。需要将扩展部署到与 VM 相同的区域才能找到它。您必须提供虚拟机的名称和位置。

下面是 ARM 模板可能的样子。

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "vmName": {
      "type": "string",
      "metadata": {
        "description": "Name of the Virtual Machine to run the extension on"
      }
    },
    "vmLocation": {
        "type": "string",
      "metadata": {
        "description": "Region id of the Virtual Machine, e.g. westus2"
      }
    },
    "tentacleOctopusServerUrl": {
      "type": "string",
      "metadata": {
        "description": "The URL of the octopus server with which to register"
      }
    },
    "tentacleApiKey": {
      "type": "securestring",
      "metadata": {
        "description": "The Api Key to use to register the Tentacle with the server"
      }
    },
    "tentacleCommunicationMode": {
      "defaultValue": "Listen",
      "allowedValues": ["Listen", "Poll"],
      "type": "string",
      "metadata": {
        "description": "The type of Tentacle - whether the Tentacle listens for requests from server, or actively polls the server for requests"
      }
    },
    "tentaclePort": {
      "defaultValue": 10933,
      "minValue": 0,
      "maxValue": 65535,
      "type": "int",
      "metadata": {
        "description": "The port on which the Tentacle should listen, when CommunicationMode is set to Listen, or the port on which to poll the server, when CommunicationMode is set to Poll. By default, Tentacle's listen on 10933 and poll the Octopus Server on 10943."
      }
    },
    "tentacleRoles": {
      "type": "string",
      "metadata": {
        "description": "A comma delimited list of roles to apply to the Tentacle"
      }
    },
    "tentacleEnvironments": {
      "type": "string",
      "metadata": {
        "description": "A comma delimited list of environments in which the Tentacle should be placed"
      }
    }
  },
  "resources": [
    {
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "name": "[concat(parameters('vmName'),'/dscTentacleExtension')]",
      "apiVersion": "2021-04-01",
      "location": "[parameters('vmLocation')]",
      "properties": {
        "publisher": "Microsoft.Powershell",
        "type": "DSC",
        "typeHandlerVersion": "2.77",
        "autoUpgradeMinorVersion": true,
        "forceUpdateTag": "2",
        "settings": {
          "configuration": {
            "url": "https://url-to-storage/OctopusTentacle.zip",
            "script": "OctopusTentacle.ps1",
            "function": "OctopusTentacle"
          },
          "configurationArguments": {
            "ApiKey": "[parameters('tentacleApiKey')]",
            "OctopusServerUrl": "[parameters('tentacleOctopusServerUrl')]",
            "Environments": "[parameters('tentacleEnvironments')]",
            "Roles": "[parameters('tentacleRoles')]",
            "ServerPort": "[parameters('tentaclePort')]",
            "CommunicationMode":"[parameters('tentacleCommunicationMode')]"
          }
        },
        "protectedSettings": null
      }
    }
  ]
} 

部署 ARM 模板

您可以从以下位置部署 ARM 模板:

后续步骤

  1. 我们计划在 2023 年 3 月 8 日星期三将触手 6.3 重新发布到我们的下载页面和 Chocolatey。由于这篇文章中描述的 Azure VM 扩展问题,我们不得不从这些来源中获取最新的信息。如果您对扩展有问题,这可能是原因。
  2. 我们计划在 2023 年 3 月底从市场上移除 Azure VM 扩展,以完成弃用过程。

结论

随着 Azure VM 扩展的退役,我们现在建议您使用 ARM 模板和 DSC 扩展在 Azure 中部署 Windows 触手 VM。这使您能够更好地控制如何部署虚拟机和触角。它还让我们更有信心地更新触手,因此我们可以为您带来更强大、更流畅的部署体验。

愉快的部署!

如何设计一个自动化的数据库部署过程——Octopus Deploy

原文:https://octopus.com/blog/designing-db-deployment-process

Designing a automated database deployment pipeline

自动化数据库部署是 CI/CD 难题的最后一块,我需要将耗时 2 到 4 小时的部署缩短到 10 分钟左右。成功地自动化数据库部署需要许多尝试,失败不是因为工具,流程只是从根本上被破坏了,它必须完全重新设计。

在接下来的两篇文章中,我将带您设计一个自动化的数据库部署过程。在本文中,我主要关注核心概念。如果你想跳过,这里有其他文章的链接:

我们所有的数据库部署文章都可以在这里找到。

在这篇文章中

典型的手动数据库部署过程

我经常看到这样的手动数据库部署过程:

  • 一个开发者在development做了一个改变。
  • 开发人员将更改添加到 Word 文档、Excel 电子表格、Trello 板(插入您选择的板工具)或一张纸上。
  • 那些变更被手工编译成一个增量脚本,以推送到teststagingproduction
  • 增量脚本交给 DBA(或拥有正确权限的人)来运行。
  • DBA 运行脚本并将结果发送回请求者。

让我们后退一步,评估一下这个过程存在的原因:

  • 没有跟踪历史的源代码控制,比如为什么要做一个变更,什么时候做的,谁做的。
  • 没有源代码控制,实施审查过程是极其困难的。
  • 如果/当评审发生时,它们发生在开发生命周期的很晚阶段,并且除非出现非常严重的问题,否则阻止部署通常为时已晚。
  • 如果没有一致的审查过程,引入次优变更的可能性会大得多。
  • 没有审查过程的历史记录详细说明是谁批准的或何时批准的。
  • 大多数公司要求职责分离,因此做出改变的人不能是部署改变的人。

归根结底是信任和执行。除非该过程是自动执行的,否则它是不可信的,没有信任,自动化就不会发生。

尝试自动化数据库部署的经验教训

下面是我从自动化数据库部署中学到的一些惨痛教训:

  • 不要一开始就包括所有人。创建一个由 2 到 4 个关键人员组成的小型工作组,包括 DBA 或数据库架构师。挑选一个试点团队来解决问题。试点团队应该向工作组派遣 1 到 2 个人(总人数在 4 到 6 人之间)。应该授权工作组作出决定。
  • 不要将特定的群体排除在工作组之外。如果您有开发人员、QA、数据库开发人员和 DBA,那么每个组都应该有一名代表加入工作组。
  • 不要花几周或几个月的时间去设计完美的流程。工作组应召开一次为期 1 或 2 天的启动会议,以创建理想流程的草案。之后,安排定期检查,看看试点团队做得怎么样。
  • 不要在动员会上分散你的注意力。划出 1 到 2 天的时间,不允许打开笔记本电脑,除非是为了查找特定问题的答案。
  • 不要专注于特定的工具。关注可用工具的核心概念。例如,现代版本控制软件支持分支和合并的审查过程,并且每个数据库部署工具都有从命令行运行的方法。
  • 不要试图一开始就自动回滚。你可以在动员会上花整整两天时间,试图找出所有可能的情况。
  • 如果你遇到困难,不要害怕寻求帮助。当我在以前的公司工作时,我们向 Redgate 寻求帮助,他们指导我们完成了这个过程。有一些咨询公司,比如 DLM 顾问公司,也可以提供帮助。
  • 不要凭空做决定。工作组应该透明,在关键点上征求反馈,并定期向每个人提供更新。

根据您公司的规模,您可能只有 4 到 6 名开发人员和一名 DBA,或者可能只有 2 到 3 个人有解决问题的愿望/带宽。在这种情况下,工作小组实际上是指任何想要加入的人。

启动会议第 1 天

第一天将专注于部署流程。

现有的流程需要改变,在某些情况下,需要创建一个全新的流程。但是你必须从某个地方开始。写下现有的过程,这样每个人都在同一页上。包括从开发人员到生产人员的所有变更步骤。在记下流程的同时,重点回答以下问题:

  1. 谁参与了这个过程?
  2. 他们有什么权限?
  3. 他们为什么会参与进来?
  4. 哪些环境有不同的流程?
  5. 为什么会不一样?
  6. 脚本运行失败会发生什么?
  7. 为什么脚本通常会失败?
  8. 谁审查脚本,何时审查?
  9. 谁需要参与每次部署?
  10. 什么不起作用,什么需要改变?

有了这些答案,是时候开始草拟理想流程了。在设计流程时,不要使用特定的工具术语。说all database changes will be reviewed by a database developer during the merge process而不是all database changes will be reviewed by a database developer via a pull request prior to merging into master。乍一看,它们几乎一模一样,但是pull requestmaster是 Git 常用的术语。

希望在第一天结束的时候,你会有一个流程的工作草案。这是前 alpha 的草稿,如果有漏洞也没关系。

启动会议第 2 天

第二天的重点是工具和细化。

既然您对想要做的事情有了一个粗略的想法,那么是时候研究现有的工具了。在你做研究的时候完善你的过程。随着学习的深入,可以添加、删除或移动步骤。

说到数据库部署工具,有很多选择。例如,如何自动部署 sql server,有 RedgateDbUpApexSQLSQL Server Data Tools for Visual Studio(SSDT)RoundhousEFlyway 等等。这很容易导致分析瘫痪,尤其是在进行并排比较的时候。

使用理想的过程,确定两到三个工具必须具备的关键特性。不要列出每个工具都支持的特性,例如,任何工具都可以以某种方式保存到源代码管理中,所以没有必要包括这些特性。

以下是一些有助于梳理需求的问题:

  1. 基于状态的数据库开发(即模型驱动) vs 基于迁移的数据库开发(即变更驱动或迁移脚本)
  2. 用于更改数据库的常用工具是什么?对于 SQL Server,通常是 SQL Server Management Studio (SSMS)或 Visual Studio?
  3. 如何检测数据库更改并将其保存到源代码控制中?
  4. 谁将做出最大的改变?DBAs?开发商?数据库开发人员?

在某些情况下,工具将满足 3 个关键要求中的 2 个。但是这个工具是免费的,很难与免费争论。同时,付费工具满足所有 3 个关键要求。在这种情况下,需要考虑一些问题。

  1. 这种缺失的特性会减缓采用速度吗?
  2. 我们可以做些什么来增加免费工具,以达到 3 个要求中的 2.5 个?
  3. 该公司过去是否尝试过使用免费工具?如果有,为什么没有被采用?

我在 Redgate vs . SQL Server Data Tools for Visual Studio(SSDT)上也有过类似的免费 vs .付费辩论。100 多名开发人员购买 Redgate 的成本高达六位数。与此同时,SSDT 是免费的,但 SSDT 与 Visual Studio 整合,而不是 SSMS。过去有几个团队曾试图采用 SSDT,但最终都放弃了。太多人更喜欢在 SSMS 而不是 Visual Studio 中进行更改。那些在 SSMS 进行变更的人最终用一个手动的过程将变更带入 SSDT,并且有一半的时间变更没有被签入到源代码控制中。我并不是说 SSDT 是一个不好的工具,只是说它不适合我们的特定需求。

试点团队、迭代和早期采用者

在启动会议和工装研究之后,是时候让试点团队接手了。他们的目标是实现工具和过程。理想情况下,可以在所有环境中部署到生产环境中。在这个过程中,他们将向工作组提供有价值的反馈,并讨论需要进行哪些迭代。试点团队不应该羞于提供反馈。他们所面临的任何小烦恼,都会在普遍采用的过程中成倍增加。

一段时间后,根据公司规模,向早期采用者团队推广该流程。这有助于进一步优化流程。将会发现试验团队做出的假设或无意的捷径,并且将会发现额外的场景并将其包括在流程中。早期采用者可以有所帮助,但不是必须的。

普遍采用和建立信任

一般的采用阶段将会是为很多人搬很多奶酪。期望得到推后。数据库是大多数应用程序的关键组成部分,一个糟糕的脚本可能会导致几天或几周的损失。

在你的过程中建立信任是很重要的。

我发现建立这种信任的两种最佳方式是手动验证和试点团队/应用程序。我们已经讨论了试点团队和应用程序。大多数情况下,建立信任需要在早期阶段将手动验证添加到流程中。在您的过程中,DBA 在进入生产之前审查 delta 脚本,当团队开始使用该过程时,为每个环境生成并审查 delta 脚本,以帮助建立信任。

当您不得不更多地迭代流程时,不要感到惊讶。每个团队和应用程序都是独一无二的,他们可能会实现一个您从未遇到过的数据库特性。

TL;速度三角形定位法(dead reckoning)

总结一下:

  • 创建一个小团队或工作组来定义流程。包括来自每个部署阶段的代表(开发人员、数据库管理员等。).工作组不应超过 4 至 6 人。
  • 确定要包含在工作组中的试验团队或应用程序。
  • 召开为期 1 至 2 天的会议,启动工作组。
    • 写下现有的流程,确定关键人员、难点和需要改变的内容。
    • 起草理想的部署流程。
    • 研究工具。
    • 在启动结束时,试点团队应该知道需要实施什么以及使用什么工具。
  • 试点团队实施新流程。
    • 一直部署到生产。
    • 迭代流程。
    • 在这个过程成功一段时间后,看看是否有人愿意成为早期采用者。
    • 与早期采用者团队一起迭代流程。
  • 普遍采用。
    • 专注于建立对流程的信任。
    • 推广到多个团队。
    • 找到痛点就迭代。

结论

本文涵盖了许多高层次的概念。在下一篇文章中,我将回顾我遵循类似过程的一段时间。它有望为这篇文章提供一些切实的东西。

下次再见,愉快的部署!

如果您喜欢这篇文章,好消息,我们有一个关于自动化数据库部署的完整系列。

您是否已经为部署设计了您的应用程序?-章鱼部署

原文:https://octopus.com/blog/designing-for-deployment

通常,部署是事后才想到的。我们构建应用程序,并在 Visual Studio 中点击 F5 来测试它们是否工作。最终,必须有人想出如何将应用程序部署到不同的环境中。根据应用程序的不同,这可能是一项简单的工作,也可能是一项极其复杂的任务。

没有快速的解决方法,但是希望下面的一些策略可以帮助简化部署。

为部署而设计

在构建应用程序的过程中,我们在构建(或准备构建)功能时,会对框架、组件和应用程序架构做出决策。在这些时候,考虑这些决策如何影响部署也很重要。问这样的问题:

  • 应用程序将安装在哪里?
  • 我们如何根据所处的环境传递不同的配置选项?
  • 它应该对运行它的机器做什么样的假设?有哪些约束?
  • 应用程序需要哪些操作系统服务和第三方依赖?谁来安装它们?
  • 组件需要按什么顺序部署?升级期间有哪些依赖关系?
  • 我们将如何处理后续版本/部署?
  • 这个绝妙的想法如何在生产中发挥作用?
  • 运营团队将如何诊断此功能中的问题?

忽略这些问题是保证部署时痛苦的好方法。

尽可能自动化

并非部署的所有部分都可以自动化,但这并不意味着您应该放弃。如果您不能说服 DBA 让您在生产中自动执行 SQL 迁移脚本,这并不意味着您不应该尝试自动化您的 web 应用程序部署。大部分自动化的部署仍然比手工部署好。

为此,Octopus 包括一个手动部署步骤。您可以运行一系列自动化步骤,等待人工执行操作,然后继续自动化步骤。

Pausing a deployments using manual steps

遵循既定惯例,或者创建自己的惯例

遵循约定,或者在没有约定的地方创建自己的约定,是减少自动化部署工作量的好方法。也就是说,练。您可以尝试遵循以下约定:

  • 将所有配置选项存储在同一个位置(如appSettings),并以相同的方式访问它们
  • 以相同的方式安装所有 Windows 服务
  • 将应用程序安装到标准路径

让您的应用程序能够自我部署

与其依赖您选择的部署工具来执行大量的配置和部署工作,不如考虑让您的应用程序能够感知部署。例如:

  • 让您的 Windows 服务项目能够自行安装/更新
  • 让您的持久层能够确定模式是否是最新的,并应用更新模式所需的任何迁移

让您的应用程序“自部署”有很大的好处,并且将您从部署工具中分离出来。

在 Octopus 中,我们在安装应用程序时调用一个Deploy.ps1脚本。我们根据环境将配置变量传递给这个脚本。理想情况下,这个脚本可以简单到告诉您的应用程序使用任何必要的配置参数来部署自己。至少,这个脚本应该可以在 Octopus 之外工作。

不要假设应用程序是固定不变的

有时会出现如下问题:

我们有一个组件[[做一些可能不是很好的想法]] 。部署工具是否支持 [[ Rube Goldberg 特性,该特性适用于这种非常奇怪的用例,但对其他人来说不是很有用]]

不要试图“绕过”问题,有时更好的解决方案是重新考虑应用程序如何工作。如果你面临一个问题,解决方案感觉过于复杂,不要排除通过改变应用程序来重新定义问题的可能性?

当然,这并不总是可能的,但有时它比你最初想象的更有可能。有时候只需要和合适的人谈谈。

以一个部署过程为目标

有时,根据正在部署的变更种类,提出许多不同的部署过程是很有诱惑力的。对于重大部署,不要废话。只是改了一些内容?机器复制这些文件。没换数据库?不要做 XYZ。

相反,尝试创建一个单一部署流程,无论您做了什么样的更改,它都可以从头到尾运行。如果某个部署步骤不需要发生,流程应该足够智能,不运行它,或者以幂等的方式运行它——这不应该是人的责任。这意味着您只需测试一个过程,通过持续地运行它,将使更大、更可怕的部署变得更可靠。

大型应用程序的自动化部署可能是一个艰难且耗时的过程。但是,如果应用程序一开始就被设计为可部署的,那么这个过程就可以变得简单得多。

最终一致性的用户界面设计——Octopus 部署

原文:https://octopus.com/blog/designing-for-eventual-consistency

Octopus Deploy 使用 RavenDB 进行数据存储。项目、发布、部署、部署日志等等都保存在嵌入式 RavenDB 数据库中。Octopus 的 UI 是用 ASP.NET MVC 编写的,大部分数据呈现都是用 Razor 完成的。

这些技术选择实际上造成了一点问题: RavenDB 索引是异步的,但是我们真的希望在呈现页面时尽可能呈现最新的信息。RavenDB 应用程序应该为最终的一致性而设计,但是标准的 ASP.NET MVC 视图/控制器会让你认为你总是在呈现最新的信息。

I love consistency

当我们第一次发布 Octopus 的 RavenDB 版本时,我们经常遇到这种错误报告:

我转到“添加机器”页面,输入机器信息,单击保存,但是当我转到“环境”页面时,我的机器没有列出。几秒钟后,我点击刷新,它就在那里。

为了解决这些问题,我将它分散在几乎所有的查询中:

.Customize(x => x.WaitForNonStaleResultsAsOfNow()) 

事实上,我做得太多了,以至于我创建了一个扩展方法来简化它:

.WaitForAges() 

这样做的好处是每个页面总是显示最新的信息。缺点是,一旦服务器有太多的文档,请求就会超时,因为索引已经过时了。这正在成为一个问题。

为最终一致性而设计

对于 Octopus Deploy V2 来说,最终一致性模型不是一个弱点,而是一个优势。

当我们呈现一个页面时,我们将避免对 RavenDB 进行任何查询。相反,我们将呈现页面所需的 HTML/CSS/JS。对于页面上的每个区域,我们将使用 SignalR 异步获取数据。结果将包括索引是否过时,如果是,我们将让用户知道(“该数据可能已经过时——最后更新于 XYZ”)。

然后,使用 SignalR,我们将订阅 RavenDB 中的更改通知,并将它们转发回应用程序,告诉它数据已经更改。

一旦页面的某个区域知道有新数据可用,它可能会:

  • 立即显示更新
  • 显示消息/刷新按钮

这种方法应该有望让 UI 感觉更快(因为我们可以快速呈现信息,而无需等待索引更新)以及感觉更实时(不再需要点击 F5)。

如何为 DevOps automation - Octopus Deploy 构建 Git 存储库

原文:https://octopus.com/blog/devops-automation-repo-design

How to structure your Git repository for DevOps automation

作为一名操作人员,当您第一次开始使用自动化时,您可能会发现到处都有 Git 库。其中一些可能非常臃肿,包含一行程序、脚本、函数、模块和配置文件。其他回购可能因数月或数年未动的破裂 CI/CD 管道而变得陈旧。

存储库设计通常是事后才想到的,这是有充分理由的。当你不知道你到底在构建什么的时候,很难知道如何构建一个东西。它不一定要一团糟,也不一定要把未来几年的工作提前投入到今天无法做出的决定中。

作为许多“混乱”代码库的贡献者,我将与您分享在开始自动化之路时,什么对我所在的团队最有效,以及如何重构集中式存储库。

从简单开始,集中自动化

|-- docs/
|   |-- failover-database.md
|   |-- sre-local-setup.md
|-- scripts/
|   |-- windows/
|   |-- linux/
|   |-- load-balancer/
|   |-- networking/
|-- ansible/
|   |-- group_vars/
|   |-- roles/
|   |-- site.yml
|   |-- windows-patching.yml
|   |-- inventory.yml
|-- terraform/
|   |-- main.tf
|   |-- provider.tf
|   |-- variables.tf
|   |-- outputs.tf 

最初,创建一个中央存储库可能是最好的开始方式。这大致相当于一个单片应用程序,其中所有的东西都是紧密耦合的。临时脚本、编排自动化和基础设施作为代码文档都存在于同一个存储库中。以你的团队命名资源库,让它成为你的团队开发的所有代码的真实来源。

保持存储库布局简单。创建通用目录来分隔存储库中不同类型的自动化。例如,为临时脚本创建一个脚本目录,为 Terraform 配置文件创建另一个脚本目录,等等。添加额外的目录,用于定义打补丁等流程的自动化。在这个阶段,不要在目录的结构和命名上花太多心思。你现在的首要目标是让它尽可能简单易用。

当代码的执行仍然是手动的时,集中式设计效果最好,例如,代码配置、脚本和编排自动化等基础设施都是从命令行下载和运行的。

将集中式存储库设计与单一的应用程序进行比较,可能会让你觉得不应该使用它。但是,如果您的团队或组织不熟悉自动化或基础设施,一般来说,这是一个很好的起点。这是你最终会摆脱的东西,但我发现这比跳到一个更成熟的设计要好得多,它会增加复杂性、混乱,并降低你的采用率。

把所有东西都放在一个地方可以让事情变得简单。如何组织文件以及应该在哪里提交代码都很清楚。很容易跟踪更改,并且有一个单独的 CI/CD 管道来排除故障。

什么时候重构存储库?

摩擦是识别约束的最佳方式,随着时间的推移,您的集中式存储库的代码库将开始变得繁重。当你转向自动执行任务并停止手动运行一切时,就会发生这种情况。这时,集中式存储库开始让事情变得复杂。

集中式存储库的最大缺点是它包含的工作流的数量。有很多 T4 的工作需要自动化,而且不是所有的工作都在同一时间运行。

例如,假设您有一个用于生成审计报告的脚本,一个用于完成故障转移的 PowerShell 模块,以及用于构建基础架构的 Terraform 代码,所有这些都在一个 repo 中完成。瓶颈不在于单一回购,而在于工作流程。这三种不同的代码基础中的每一种都将在不同的时间发生变化和发展。

正是在这一点上,整个代码库的最新版本变得有问题。如果对审计脚本的更改混入了一些 Terraform 更改,可能会发生不好的事情。结果,开发变慢了,这正是你需要重新评估你的存储库设计的时候。

简而言之,有两个指标是时候重新设计了:

  • 发展放缓。
  • 移除代码的手动执行会增加风险。

一种解决方案是为每个单独的工作流创建一个存储库,虽然这是一个简单的解决方案,但不是最好的。

如何拆分存储库?

每样东西和每个人都有一个仓库!这通常是集中式存储库出现问题时的第一反应。然而,这也带来了一系列挑战。相反,我建议你跟随改变

编写自动化是为了进行更改、报告更改或测试更改。当需要重构您的存储库时,从在系统中映射这些变化开始。使用这些数据将有助于你做出决定。

价值流图是一种用于分析为客户带来成果所需的信息流、人流和物流的技术。通常以产品或服务的形式。这是一个起源于精益制造方法的概念,但在 DevOps 文献中也很普遍。

这种分析可以像你做的那样复杂,但要保持简单,坚持识别过程中的步骤,而不是浪费和效率。你的目标是可视化当前的过程,而不是优化它。

【T2

对于中央存储库中的每个不同流程,请完成此工作流练习。例如,假设您已经自动化了一个操作系统修补流程。代码位于中央存储库中,每月运行一次。您还可以在同一个存储库中将基础设施作为代码配置,但是它是按需运行的,没有时间表。这些过程中的每一个都有不同的工作流程,并且很适合分离到它们自己的存储库中。

创建更多的存储库是有帮助的,但是也会带来更多的复杂性。有权衡,你需要取得正确的平衡。为了组织代码而将代码转移到另一个存储库不会激励任何人。然而,如果移动代码意味着一项任务可以完全自动化,并且某人不必在凌晨 3:00 醒来修补服务器。那么绝对值得。

使用简单的工作流程工具,如 draw.io 来起草工作流程。与您的团队分享工作流程以获得反馈。在您映射了流程之后,开始将该流程所需的所有代码转移到不同的存储库中。同样,只有当代码增加价值时,才解耦代码。

结论

从简单开始是开始你的自动化之旅的最佳方式。通过使用包含所有自动化流程的集中存储库,避免过度设计。开发一慢下来,摩擦一增加就重构。使用价值流图来确定中央存储库中存在的工作流。将具有最高投资回报(ROI)的代码解耦到您的团队,并增加最大的价值。

Octopus 将参加 2019 年伦敦 devo PS Days-Octopus 部署

原文:https://octopus.com/blog/devops-days-london

Octopus Deploy at DevOps Days London illustration

在 Octopus Deploy,我们对自动化充满热情,无论是代码、数据库还是基础设施,这就是为什么我们很高兴地宣布,我们将于 2019 年 9 月 26 日至 27 日在英国伦敦威斯敏斯特 QEII 中心赞助 DevOps Days London

DevOpsDays 是一个全球性的由社区举办的技术会议系列,涵盖了软件开发、IT 基础设施运营以及它们之间的交集等主题。它是由社区志愿者为了社区的利益而经营的。

DevOpsDays London 的形式包括在每个活动的上午进行 30 分钟的单场讲座,随后是 Ignite talks (5 分钟,自动转发)。下午剩下的时间,我们在露天场所度过,这被认为是活动的一个重要部分,也给了我们与八达通用户交谈的机会。

现在注册还来得及!可以用代码 DODL19_OCTOPUS20 打八折,在这里可以注册

如果你已经来了,一定要和团队打招呼,并拿一些贴纸。我们也有一些章鱼 t 恤要送出,所以早点来哦!

DevOps 原则如何驱动 Octopus 文档- Octopus 部署

原文:https://octopus.com/blog/devops-documentation

DevOps practices power Octopus documentation

我们有详细的端到端 CI/CD 指南到 Octopus 文档来帮助团队配置他们的交付管道。我们将 DevOps 原则应用到软件文档的过程中,这使我们能够创建有用的分步指南,每个场景都有自动截图和截屏。

如果您曾经尝试过通过电话为朋友、同事或客户的技术问题提供支持,您就会知道这种对话是多么低效:

点击绿色的大“确定”按钮。向下滚动页面。它在右下角。不,不是那个按钮。不,也不是那个。不,不要单击后退按钮。唉…好吧,我们重新开始。

如果你曾经亲自向某人提供过同样的支持,这个过程会更有效率,因为你可以在发布指示时指向屏幕:

点击那个。点击那个。向下滚动。点击那个。不,不是那个。是的,点击那个。

两个人看着同一个屏幕提供的共享上下文立即消除了模糊性,整个过程也流畅了许多。

Octopus Deploy 是一个集成工具,位于十几个不同的(同样复杂的)平台和工具的中间。当我们开始端到端指南的工作时,我们知道我们需要提供完整的旅程,而不是假设人们知道全局。我们还需要向用户展示如何做事情,而不是告诉他们。

这个项目的成果就是章鱼指南

Octopus Guides 允许您选择您的技术堆栈,并看到一个专门的指南,引导您完成从编译代码到将其部署到目的地的整个过程。我们还没有完成每一个组合,但是如果你找到一个还没有写出来的组合,你可以投它一票。

创建定制的软件文档

为了再现两个人看着同一个屏幕的效率,每个指南都有超过 100 个突出显示的图像,并且整个过程都被显示每次鼠标点击的截屏所捕捉。因为每个指南都是为选定的软件堆栈量身定制的,所以您不必在过程的下一步进行搜索。

创建这一套定制的软件文档不是一项简单的任务。在撰写本文时,我们有大约 60 个这样的个人指南,其中包含超过 6,000 个突出显示的图像、25 小时的视频和近 300,000 个单词,记录了大约 16 种技术堆栈的各种组合。

指南中包含的许多应用程序是按月或周发布的,理想情况下,我们的截屏会保持最新。这个内容是由两个人组成的团队(我是作者/开发人员,一个编辑来审核和编辑内容)在几个月的时间里创建和维护的。

为了实现这一点,我们需要一个能够远远超越传统的编写、提交、转换和发布工作流的解决方案。由于无法投入更多的人来解决这个问题,我们严重依赖于 DevOps 实践,例如:

  • 自动化测试(确保我们记录的过程如所描述的那样工作)。
  • 作为代码的基础设施。
  • 生成截图和视频的自动化管道。

简单的例子

为了演示我们如何创建指南,我将创建一个简单的示例来记录执行 Google 搜索的情况:

  1. 打开 https://google.com 的。

  2. 在文本框中输入搜索词,然后单击 Google 搜索按钮:

  1. 随后将显示搜索结果:

这是一个简单的例子,但是它说明了我们实现的两个特性:

  1. 我们有一个截屏显示与浏览器的交互,跟踪每一次鼠标点击和表单输入。这个视频是节目的一个重要部分,不要告诉哲学,因为它确切地展示了文档描述的内容。

  2. 我们在搜索屏幕上交互的元素在截图中用荧光绿框突出显示。这相当于坐在某人的肩膀上指向屏幕,而不是依赖于视觉用户界面的笨拙的书面描述。

自动化流程

该视频和截图可以手动创建和编辑,但鉴于我们正在为扩展到几十个(如果不是数百个)指南的工作流构建基础,我们需要自动创建这些资产。

自动化这些资产的创建和更新需要四项服务:

  • 一个视频托管服务,理想情况下可以就地更新视频。
  • 图像托管服务。
  • 一个用于编写 web 浏览器交互脚本的工具。
  • 执行脚本的服务。

对于八达通指南,我们使用:

要了解这一点,请看一个样本项目。这个项目包括两个由 GitHub Actions 执行的工作流:一个用于捕获截屏,另一个用于生成截屏。

捕捉截屏的工作流代码可以在 OctopusSamples GitHub repo 中找到。它从一些样板 YAML 开始,命名工作流,指定何时运行它,并定义在 Ubuntu 虚拟机(VM)上执行的名为build的作业。

push选项意味着对存储库的每次提交都会触发一次构建,而schedule选项被设置为在 UTC 每天午夜运行构建。

按照这样的时间表运行构建意味着我们知道我们的截图和视频是最新的,即使正在使用的应用程序是更新的。或者在这个例子中,它确保我们的文档捕获最新的 Google doodle。

这很重要,因为我们不希望我们的文档充满陈旧的媒体。

name: Google Agile Docs Video
on:
  push:
  schedule:
    - cron:  '0 0 * * *'
jobs:
  build:
    runs-on: ubuntu-latest 

接下来,我们定义一组与作业中的步骤共享的公共环境变量。这些变量定义了用于上传图像的 AWS 凭证、用于替换视频的 Wista 凭证和视频 ID、用于配置 WebDriver 脚本工具的布尔标志,以及用于定义浏览器 UI 在显示 HTML 内容之前占用的像素数的硬编码偏移量。

因为浏览器只能报告元素相对于浏览器窗口的位置,而不是相对于屏幕上的位置,所以垂直偏移值被添加到相对窗口位置,以找到元素的绝对屏幕位置,这又允许 WebDriver 工具将鼠标光标移动到该位置:

 env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
      WISTIA_USERNAME: ${{ secrets.WISTIA_USERNAME }}
      WISTIA_PASSWORD: ${{ secrets.WISTIA_PASSWORD }}
      WISTIA_MEDIA_ID: eecl0uod01
      MOVE_TO_MOUSE_CURSOR: true
      DISABLE_HIGHLIGHTS: true
      DISABLE_SCREENSHOTS: true
      DISABLE_VIDEO: false
      DUMP_OPTIONS: true
      MOUSE_VERTICAL_OFFSET: 74 

接下来,我们开始定义组成工作流的步骤。第一步是从 GitHub repo 中检查代码:

 steps:
      - uses: actions/checkout@v1 

下一步使用 Puppet 来安装我们需要的工具,比如用于上传图像的 AWS CLI,以及用于处理视频的 VLC 和 FFmpeg。

我们使用 Puppet 而不是原生 GitHub 动作有几个原因:

  1. Puppet 可以在开发 VM 中的 GitHub 动作之外运行,以调试流程。
  2. Octopus 指南记录的场景涉及复杂的基础设施初始化:CI 服务器、Octopus 部署、Artifactory 之类的包存储库、带有 Minikube 的 Kubernetes 集群和 web 服务器。

Puppet 非常适合部署这种基础设施,而 GitHub Actions 往往局限于构建和测试代码。

 - name: Configure VM
        run: ./puppet/install.sh setup.pp 

下一步运行我们定制的 Selenium WebDriver 脚本工具。这个工具的代码可以在这个 GitHub repo 中找到。如果“WebDriverTraining”听起来像一个奇怪的名字,那是因为这段代码最初是作为描述从头开始创建 WebDriver 测试工具的过程的博客系列的一部分编写的。该项目非常适合为指南生成资产,因此我们将其扩展并捆绑到一个 Docker 映像中,以便与 GitHub Actions 集成。

在这里,您可以看到许多全局环境变量通过JAVA_OPTS环境变量作为 Java 属性传递。这些属性是用-D参数、属性名和属性值定义的:

  • moveCursorToElement:将鼠标光标移动作为 WebDriver 脚本的一部分。
  • disableHighlights:禁止在元素上放置绿色高亮。
  • disableScreenshots:禁用截图。
  • mouseMoveVerticalOffset:定义浏览器 UI 顶部小工具的高度。
  • CucumberAlias-[Alias Name]:定义一些在脚本中使用的别名。

最后,定义要传递给 Docker 映像的参数。参数被传递给 Cucumber CLI,这个 Docker 映像实现了它。第一个参数启用进度插件(--plugin progress),并传递要运行的特征文件的路径(/github/workspace/google.feature):

 - name: Run script
        uses: docker://mcasperson/webdriver
        env:
          JAVA_OPTS: >-
            -DmoveCursorToElement=${MOVE_TO_MOUSE_CURSOR}
            -DdisableHighlights=${DISABLE_HIGHLIGHTS}
            -DdisableScreenshots=${DISABLE_SCREENSHOTS}
            -DmouseMoveVerticalOffset=${MOUSE_VERTICAL_OFFSET}
            -DdisableVideoRecording=${DISABLE_VIDEO}
            -DdumpOptions=${DUMP_OPTIONS}
            -DCucumberAlias-ExternalVideoDir=/github/workspace
            -DCucumberAlias-ExternalScreenshotDir=/github/workspace
        with:
          args: --plugin progress /github/workspace/google.feature 

在这个步骤运行之后,我们在工作区目录中有一个或多个 AVI 文件。下一步是使用 VLC 将文件转换成 MP4 格式,并将它们组合成一个视频。然后,它使用 FFmpeg 加速视频,使它们更容易观看。执行此操作的脚本可以在 OctopusSamples Repo 中找到:

 - name: Process video
        run: ./process-video.sh 

最后一步是替换已经上传到 Wistia 的视频。替换视频意味着任何嵌入它的现有 HTML 页面将显示新内容,而无需进一步编辑。

不幸的是,Wistia API 没有提供替换视频的功能,但是可以通过网站来实现。对于我们为自己的文档执行的 WebDriver 脚本来说,这是一个完美的用例。在这种情况下,我们不会从脚本中生成任何媒体资源,因此所有屏幕截图和视频录制都已被禁用:

 - name: Replace Wistia video
        uses: docker://mcasperson/webdriver
        env:
          JAVA_OPTS: >-
            -DmoveCursorToElement=false
            -DdisableHighlights=true
            -DdisableScreenshots=true
            -DdisableVideoRecording=true
            -DdumpOptions=${DUMP_OPTIONS}
            -DCucumberAlias-ExternalWistiaUsername=${WISTIA_USERNAME}
            -DCucumberAlias-ExternalWistiaPassword=${WISTIA_PASSWORD}
            -DCucumberAlias-ExternalMediaID=${WISTIA_MEDIA_ID}
            -DCucumberAlias-ExternalVideoDir=/github/workspace
        with:
          args: --plugin progress /github/workspace/replace-video.feature 

生成截图的工作流程非常相似,只是传递给 WebDriver Docker 容器的标志禁用视频并启用高亮显示和截图,最后一步是将截图上传到 AWS S3。截图工作流程的 YAML 可在 OctopusSamples Repo 中找到。

Selenium WebDriver 示例脚本

GitHub Actions 工作流完成后,我们已经配置了生成截图和视频所需的所有工具,并将结果上传到 Wistia 或 AWS S3。拼图的最后一块是 WebDriver 脚本。

我们提到 WebDriver Docker 容器正在运行 Cucumber。Cucumber 是一个库,它读取小黄瓜脚本并执行与步骤相关的代码。但是,如果您不熟悉 Cucumber 或 Gherkin,也不用担心,因为这些脚本很容易理解,因为它们是用类似于简单英语的语言编写的。

google.feature文件如下所示:

Feature: Search with Google

  Scenario: Open Page
    And I open the shared browser "FirefoxNoImplicitWait"
    And I maximize the window
    And I open the URL "https://google.com"

  Scenario: Perform Search
    Given I set the following aliases:
      | Search        | //input[@name='q']              |
      | Google Search | //input[@value='Google Search'] |
    And I start recording the screen to the directory "ExternalVideoDir"

    When I populate the "Search" text box with "Octopus Deploy"

    And I highlight outside the "Search" text box with an offset of "2"
    And I highlight outside the "Google Search" button with an offset of "2"
    And I save a screenshot to "#{ExternalScreenshotDir}/google/010-search.png"

    And I click the "Google Search" button

    And I save a screenshot to "#{ExternalScreenshotDir}/google/020-search-results.png"

    Then I verify the current URL matches the regex "https://www.google.com/search.*"

  Scenario: Close browser
    Then I close the browser 

该文件由一个顶级特性和几个子场景标识。第一个场景打开 web 浏览器,将其最大化,然后打开 Google 主页:

 Scenario: Open Page
    And I open the shared browser "FirefoxNoImplicitWait"
    And I maximize the window
    And I open the URL "https://google.com" 

第二个场景是我们执行搜索的地方。场景中的第一步是定义别名(键/值对),将人类可读的名称分配给 XPath,该名称标识页面上我们将与之交互的元素。在本例中,我们将与两个元素进行交互,即搜索文本框和搜索按钮:

 Scenario: Perform Search
    Given I set the following aliases:
      | Search        | //input[@name='q']              |
      | Google Search | //input[@value='Google Search'] | 

下一步开始记录到别名ExternalVideoDir中定义的目录中(除非已经从命令行禁用了所有记录)。与上面定义的别名不同,这个别名被定义为 GitHub Actions 工作流文件中的JAVA_OPTS环境变量的一部分,设置为-DCucumberAlias-ExternalVideoDir=/github/workspace:

 And I start recording the screen to the directory "ExternalVideoDir" 

我们现在开始通过在搜索文本框中输入查询来与页面进行交互。注意这里我们通过别名Search来引用我们正在交互的元素:

 When I populate the "Search" text box with "Octopus Deploy" 

接下来的两个步骤在页面上的元素周围添加荧光绿色高亮显示(除非从命令行禁用了所有高亮显示)。这些重点用于帮助读者快速找到文档中引用的元素:

 And I highlight outside the "Search" text box with an offset of "2"
    And I highlight outside the "Google Search" button with an offset of "2" 

随着高光的应用,我们捕捉一个截图。这里,我们使用内插法从别名ExternalScreenshotDir的值和固定路径/google/010-search.png创建了截图的路径:

 And I save a screenshot to "#{ExternalScreenshotDir}/google/010-search.png" 

然后我们单击 search 按钮,别名为Google Search:

 And I click the "Google Search" button 

我们抓取了搜索结果的另一个截图:

 And I save a screenshot to "#{ExternalScreenshotDir}/google/020-search-results.png" 

这个场景的最后一步是验证结果页面的 URL 是否匹配正则表达式。这种验证为我们提供了一种保证,即我们正确填写了搜索表单:

 Then I verify the current URL matches the regex "https://www.google.com/search.*" 

最后一种情况是关闭浏览器。这也将停止任何视频录制:

 Scenario: Close browser
    Then I close the browser 

同样的脚本运行两次,一次是在禁用所有突出显示的情况下捕获视频,另一次是在禁用视频录制的情况下捕获屏幕截图。这两个阶段的输出为我们提供了突出显示的截图和截屏,我们可以在最终的文档中引用它们。

将 DevOps 引入软件文档

虽然这是一个微不足道的例子,但我们在这里创建的工作流程基本上与我们在 Octopus Guides 中制作数千个截图和数小时视频的流程相同。这里展示了几个关键特性,它们使我们能够扩大文档的制作规模。

近乎无限的执行规模

整个过程是自动化的,并使用 GitHub Actions 进行调度。这使我们能够通过并行运行多个工作流来进行扩展,只受您希望在计算成本上花费多少以及 GitHub 强加的限制的限制。

GitHub 负责配置和清理构建环境,所以我们需要做的就是设置合适的时间表。

可验证的而非期望的文件

你有没有读过技术文档,认为作者在描述他们认为产品应该如何工作而不是 T2 如何工作?或者你碰壁了,因为编写文档的开发人员忘记提到他们几年前安装的一个依赖项,并且根本没有意识到它对过程的重要性?

通过使用 Puppet 和 GitHub Actions 提供的空白虚拟机(不可变的基础设施),我们迫使自己每次都要从头开始重建我们正在记录的环境。

虽然这个例子没有安装任何本地基础设施,只访问了 Google,但是 Octopus Guides 工作流在每次运行时都会安装 CI 服务器、数据库、构建工具和 web 服务器。这意味着指南可以可验证地演示 Octopus 和相关工具实际上是如何工作的,而不是描述事情是如何假设工作的。

自动化测试和可测试文档

尽管文档仍然是手写的,但现在文档中概述的步骤与我们用 GitHub 操作执行和验证的 Gherkin 脚本之间有了一对一的映射。这意味着我们现在有了由自动化测试支持的文档,这在制作我们包含在 Octopus 指南中的大量内容时是必不可少的。

安排工作流的定期运行意味着我们可以在任何可能破坏我们脚本的产品更新时得到通知,因此需要更新文档。

总是最新的软件文档

虽然这些浏览器脚本的目的是生成我们的文档所使用的截图和视频,但它们本质上也是端到端的测试。

如果脚本失败,例如,如果 HTML 元素不再可用或者手动验证步骤不成功,GitHub 操作失败,我们会得到通知,并可以确定我们的文档的某些方面是否需要更新。

另一方面,如果脚本通过,我们可以确信我们收集的截图和视频是有效的,并且它们可以立即更新。

在实践中,这意味着如果您的 Puppet 脚本默认安装任何软件的最新版本,并且脚本通过,则无需任何手动干预就可以将截图和视频推送到 live 文档中。这解决了文档中出现陈旧图像的问题,并消除了手动更新此类媒体的成本。

因此,如果詹金斯曾经刷新他们的用户界面,你可以放心,章鱼指南将保持最新。

结论

通过采用 DevOps 最佳实践,如代码、自动化测试和管道等基础设施,有可能简化创建和维护大量高质量文档的一些最手工、繁琐和昂贵的方面。

仅仅经过几个月的工作,一个 2 人团队就制作了数千张截图、数小时的视频和数十万字,所有这些都是自动生成和验证的。

如果你是八达通的顾客,我们希望你会发现这些新的指南很有价值。如果你对制作类似的内容感兴趣,下面列出了本博客中提到的资源:

愉快的部署!

在州政府实施 devo PS-Octopus 部署

原文:https://octopus.com/blog/devops-in-state-government

Illustration showing an infinite feedback loop surrounding a government building

政府通常是行动缓慢的官僚机构,但这并不意味着不可能在政府机构内部实施更好的流程。

2011 年,我被一家小型美国州政府机构聘为配置经理。我有一个挑战性的任务,花了好几年才完成:

  • 自动化手动流程,提高软件部署的可靠性。
  • 减少交付软件的时间长度。
  • 消除周末部署的需要。

在这篇文章中,我将介绍我实现这一目标的方法,以及在类似环境中你可能会遇到的一些常见陷阱。

优先处理最大的问题

我加入的机构有很多问题,所以我做的第一件事就是学习一切是如何构建和运作的。然后,我优先考虑我要改善的第一步。该机构有几个内部应用程序,它们的部署过程使它们很容易出现问题。Web 应用构建是在开发人员的机器上完成的,被压缩并复制到一个文件共享中。数据库更改是通过用一个列出执行顺序的文档压缩一堆脚本来处理的,然后这个文档被复制到一个文件共享中,供 DBA 获取。不可避免地,部署会因为以下任何一个原因而失败:

  • 一个开发者没有提到需要安装在 web 服务器上的第三方依赖。
  • 数据库更改的脚本没有针对生产数据库的当前状态进行测试。
  • 老式的人为失误。

事情必须改变。

早期胜利

我从构建过程的开始就开始了,这样我就可以消除那句格言:“在我的机器上工作,现在是操作问题。”该团队使用 Microsoft Team Foundation Server 进行源代码控制,这意味着构建控制器技术已经存在。我安装了控制器和几个代理,这样所有的软件都是在独立的机器上构建的。这突出了存在于开发人员机器上并且需要安装在服务器上的依赖性。

接下来,我编写了几个小的控制台应用程序来部署 web 代码和数据库。第一个使用 Microsoft Web Deploy 来自动化 Web 代码的一致部署。我们仍然手动更新连接字符串,这不是很好,但这是一个开始。第二个控制台应用程序在单个事务中运行一系列数据库脚本,并在出现故障时回滚数据库部署。这种方法减少了错误率和部署时间,因为数据库管理员不再需要打开脚本并手动执行它们。

有了这两项改进,怀疑和不愿改变的情绪开始消退。后来,我们将控制台应用程序合并到一个自动化部署解决方案中,进一步降低了部署失败率,加快了部署过程,几乎消除了周末工作的需要。这一进展让开发人员和操作人员更加高兴。

该团队使用我的内部部署解决方案,直到一个承包商演示了他用于自动化部署的工具 Octopus Deploy。我不愿意放弃我的创作,但我也承担了数据团队主管的责任,这意味着我有更少的时间来编码,并且我无法跟上功能需求。另一方面,Octopus Deploy 有一组开发人员专职从事这项工作。抛开我的骄傲,我只用了几个星期就用 Octopus 复制了我的解决方案的功能,几个月后 Octopus 就被完全采用了。

我很高兴用我开发的内部工具实现了一些早期改进,但从长远来看,转向现成的解决方案是正确的。

从对抗到合作

随着我们自动化了更多的流程,团队之间的紧张和持续的相互指责开始缓解。有了定义良好的自动化流程,团队开始一起解决问题而不是寻找互相指责的方法。起初这并不容易,但我通过与团队单独交谈,让他们参与进来,然后一起就新流程达成一致,从而与团队建立了信任。

不断进步和后续步骤

此时,我们的大多数开发和部署流程都是自动化的,下一步是审查我们在开发和运营方面的优先事项。困扰我们的一个问题是不一致的环境。我了解了代码基础设施,并立即接受了这个概念。由于对任何现有技术(Chef、Puppet、Ansible、PowerShell DSC 等)都没有经验,我决定尝试 PowerShell DSC(理想状态配置),并且我很快就明白了为什么所有 PowerShell 课程都像这样说...还有 PowerShell DSC,但这本身就是一门完整的课程。”

Octopus Deploy 给了我一次很棒的 PowerShell 体验,但 DSC 是一种不同的动物。经过一点学习,我可以在几分钟内演示如何将一个裸机服务器(说实话,是一个 VM)配置成一个正常工作的 IIS 服务器。不仅如此,我还可以将 Octopus Deploy 的部署能力与 PowerShell DSC 结合起来,将配置推送到服务器,就像应用程序部署一样!既然已经有了 web 管理员,我转向了数据库管理员。与 DBA 团队合作,我们创建了一个 DSC 脚本来安装、配置和维护 SQL 服务器,并将其与 Octopus 连接起来。DBA 团队现在可以随时监控他们的服务器,并根据需要进行更改。这减少了运营团队和 DBA 团队之间的摩擦。

DSC 还减少了操作和应用程序开发之间的摩擦,因为操作不再需要参与 IIS 或 SQL Server 的安装或配置;他们的工作只关注硬件和虚拟机(VM)的运行状况。DSC 是通过 Octopus Deploy 使用服务帐户执行的,这意味着非运营人员不再需要服务器的管理员权限,这让安全人员非常高兴。因为 DSC 是自文档化和版本控制的,如果操作人员需要了解某些东西是如何配置的,他们可以咨询版本控制。

(最终)结果

这些都不是一夜之间发生的。在这一点上,我们处于 2019 年初,接近我在州政府的职业生涯的尾声,但我已经实现了持续集成(CI ),每次执行签入时都会自动运行构建。大多数项目已经实现了连续交付 (CD),因此在 CI 构建完成之后,它会自动部署到较低级别的环境中,供测试人员和业务分析师开始他们的批准过程。我自动化了以下内容:

  1. 部署 ASP.NET 网站代码。
  2. Windows 服务。
  3. 数据库部署。
  4. SQL Server Reporting Services(SSRS)报表。
  5. SQL Server Integration Services(SSIS)包。
  6. 控制台应用程序。
  7. Java 应用到野花。

我记得一位开发人员的称赞,他说他喜欢这样的事实,他可以点击合并,去喝咖啡,当他回来时,他的应用程序已经部署好了。

结论

在政府组织中引入变革和 DevOps 概念可能会很慢,也很有挑战性,但这绝对是可能的。我的成功之处在于:优先处理最大的问题,购买工具进行简化和标准化,注重沟通和协作,让其他团队参与进来,我们不断取得进步,让我们一个接一个地克服障碍。

浏览 DevOps 工程师手册了解有关 DevOps 和 CI/CD 的更多信息。

DevOps 和平台工程- Octopus 部署

原文:https://octopus.com/blog/devops-platform-engineering

在这篇文章中,你会发现平台工程在你更广泛的软件交付过程中的位置。您将了解平台工程如何与开发运维流程协同工作,以及为什么开发运维与平台工程都能帮助您的组织实现高性能。

DevOps 的快速版本

DevOps 源于开发人员和运营人员一起工作的简单想法。这在许多组织中变得很难做到,因为这些团队有相互冲突的目标。

组织将目标与每个团队的专长相结合。运营团队需要保持系统稳定,而开发人员必须更频繁地交付更多价值。当团队独立工作时,来自开发人员的变更的增加降低了系统的稳定性。你可以看到这是如何为冲突创造条件的。

您可以通过让开发人员和运营人员更好地协作来克服这些相互冲突的目标。当人们尝试这样做时,他们发现有可能在更短的时间内交付更多的变更增加可靠性。

10 年来,由于 Puppet 和 DORA 的广泛研究,“开发人员和运营人员一起工作”这一模糊的价值陈述已经发展成为一套定义明确的能力。

DevOps 结构方程模型描绘了研究中发现的能力和关系。它最初是在《T2 加速》一书中描述的,多拉继续更新它,作为他们研究计划的一部分。

The 2021 DevOps structural equation model

2021 年 DevOps 结构方程模型

该模型对于寻找改进机会的团队和希望采用 DevOps 并获得高绩效好处的组织很有帮助。你可能见过这个图表的旧版本,它的方框更少。

正如你所看到的,2021 年的模型充满了特定功能的想法,你可以采用这些功能来成为更多的开发人员。如果你感到不知所措,请阅读我们 DevOps 工程师手册中的如何开始使用连续交付

该模型中的关键观点是文化对组织的技术绩效及其商业和非商业目标绩效的重要性。

在 2022 年,DevOps 已经发展成为:

  • Developers and ops working together
  • 一组定义明确的技术和非技术能力
  • 用全系统的方法评估你的成功

如果你已经存在足够长的时间,你可能会注意到 DevOps 鼓励的许多变化看起来就像我们在开发和运营筒仓被创建之前如何开发系统。

专家团队的建立是有原因的,所以当我们后退并尝试另一条道路时,你应该确保你解决了那些最初的问题,而没有重现令人讨厌的副作用。规模化和专业化的问题依然存在,那么我们如何健康的克服呢?

进入平台工程团队。

平台工程

尽管围绕 DevOps 涌现了许多新的团队和职位,但平台工程团队可能是最符合 DevOps 理念和目标的。

平台团队与开发团队合作,创建一个或多个代表一组受支持的技术选择的黄金路径。这些途径不会阻止团队使用其他东西。路径鼓励联合,而不是在开发团队中实施集中决策。平台团队创建易于使用的自助工具供开发团队使用,而不是像“创建测试环境”那样去捡票。

平台工程的一个关键部分是将开发者视为客户,解决他们的问题并减少摩擦,同时倡导采用一致的技术选择。例如,假设您的组织在运行 MySQL 数据库方面有丰富的经验,并且已经找到了解决以下问题的方法:

  • 缩放比例
  • 备份
  • 维护
  • 安全性
  • 分身术
  • 部署
  • 测试数据库

选择 MySQL 的团队只需按一下按钮,就可以免费获得所有这些。另一个团队可能仍然需要使用不同的东西,但是当他们离开这条道路时,他们将对他们的选择负责。

选择黄金路径可以加速您的软件交付,让您专注于差异化工作,并在出现问题时为您提供支持渠道。作为开发人员,您的时间最好花在为客户提供价值的特性上,而不是建立构建、环境和其他类似的活动。

平台工程可以简化许多任务:

  • 建造管道
  • 测试和生产环境
  • 自动化部署
  • 测试框架
  • 记录和监控
  • 安全功能

当您扩大您的软件交付团队时,平台工程减轻了您的操作负担。总体而言,你需要更少的这些难以找到的平台工程师,并且通过在平台团队中工作,他们可以产生比他们嵌入到开发中更大的影响。

平台工程帮助您的组织扩展其软件交付,而不会失去一些最好的小团队利益。

DevOps 和平台工程

如您所见,平台工程与 DevOps 互为补充,而非竞争。为了提供这种积极关系的进一步证据,DevOps 报告的傀儡状态发现 DevOps 高绩效者比低绩效者更有可能拥有平台工程团队。

【T2 Chart showing DevOps teams using platform engineering by performance category, table of data follows

种类 %与平台工程
低的 8%
中间的 25%
高的 48%

平台工程本身并不能提供完整的性能组织视图。DevOps 结构方程模型向我们展示了平台团队范围之外的领导、管理、文化和产品能力。这就是为什么平台工程属于一个更广泛的过程,如 DevOps,而不是提供一个替代品。

与 DevOps 一起使用,平台工程是扩展软件交付能力的优秀工具。

DevOps 希望您:

  • 测量整个系统的性能
  • 缩短和扩大反馈回路
  • 创造一种不断学习和改进的文化

平台工程希望您:

  • 平稳的开发体验
  • 创建支持自助服务的工具和工作流
  • 让开发人员更容易实现系统质量属性(比如性能、可观察性和安全性)

结论

随着您的软件交付团队的成长,您必须小心地管理规模的复杂性。一些组织通过限制团队的自主性来限制复杂性,但是平台工程提供了一种在保持开发团队自主性的同时驯服复杂性的机制。

进一步阅读

愉快的部署!

DevOps 阅读清单:选择你的下一本书

原文:https://octopus.com/blog/devops-reading-list

你喜欢书。你想更多地了解 DevOps。要么你是这个话题的新手,要么你想扩大你的知识面,但是你被这些选项淹没了。好像每个人都有自己的看法,你不知道从哪里开始,也不知道先/下一步读什么。

或者,您可能已经是 DevOps 的拥护者,您想为朋友或同事推荐一本书,但您并不了解最新版本。你应该把哪本书放在他们的鼻子下?

这篇文章是给你的。

以下是一些最受尊敬的 DevOps 书籍的列表,以及我个人对你(是的,你,特别是你)应该首先阅读哪本书的建议。如果你时间紧迫,试试我们的推荐工具吧,它是受这篇文章的启发。

如何阅读这个帖子

bookshelf

这篇文章太长了,你不能从头到尾看完。它不应该被这样理解。相反,把这当成一本“选择你自己的冒险”的故事书,我会陪你到你最合适的部分。

接下来是一个简单的决策树,旨在将你直接指向我为你推荐的个人书籍。当你进入决策树时,我鼓励你回答问题,看看它们会把你引向何方。希望你最终能得到一本引起你兴趣的书籍推荐。

有些问题有点复杂,所以在决策树下面,我提供了关于这些问题的更多信息/说明。如果这些澄清有助于你做出决定,那很好,否则,你可以跳过它们。

接下来,我为我推荐的每本 DevOps 书写了一个简短的总结。在你完成决策树练习后,我鼓励你通过链接直接找到我对你最感兴趣的书的描述。在那里,您将找到更多信息/评论和购买选项的链接。

我在这篇文章的结尾提到了几个不适合决策树的额外荣誉,以及一些最终的想法。

我希望当你读完的时候,你已经准备好去买一本你喜欢的书,在你的时间表中划出几个小时,开始阅读。我可以毫不夸张地说,当我这么做的时候,其中的一本书让我的职业生涯变得更好。

要找出哪本书让我走上了这条路,以及我在为这篇文章做研究时发现了哪本书,你必须继续读下去。

选择自己的冒险!决策树

在完成这个练习之前,有一个重要的注意事项:

这并不完美。可能行不通。

我怎么强调这一点都不为过。下面的许多书是重叠的。有些问题有点做作,经常故意低估复杂性或夸大差异,以制造简单和快速推荐的假象。请不要太认真。

此外,这不是一个详尽的标题列表。在整理这份清单时,我尽可能多地寻求反馈,但最终还是有那么多好书,我无法一一列举。一些读者会说,我应该包括这个或排除那个,但我希望绝大多数人会同意,以下所有文本都受到广泛尊重。

先不说警告,现在是你自己回答这些问题的时候了,看看你的结局如何。

【T2 decision_tree

(点击图示放大。)

如果您希望澄清以上决策树中突出显示的问题,您可以在下面找到更多指导。如果你不需要任何澄清,跳到我对这些书的总结。

对加载问题的澄清

问题 1:精益/敏捷基础还是实践 DevOps?

DevOps 建立在巨人的肩膀上。在 DevOps 之前,有持续交付,敏捷和极限编程,TDD 和精益。为了真正理解 DevOps,了解构建 DevOps 的理念是很有用的。

问问你自己,现在,你对了解 DevOps 的基本思想还是现状更感兴趣?

回到决策树

问题 2:精益原则还是敏捷实践?

敏捷运动(在 DevOps 运动之前)本身受到丰田生产系统(TPS)和精益思想的严重影响。TPS 和精益本身就是巨大的主题,值得他们自己去阅读。他们指的是制造业和供应链管理的一场革命,这场革命可以追溯到 20 世纪 70 年代,并受到日本汽车行业的严重影响。

几十年后,敏捷运动认识到,20 世纪 90 年代的 IT 正遭受着与 1970 年前的汽车行业相似的问题。为制造业设计的各种精益原则被重新构想,以便在 IT 中发挥作用。这些已经被编纂成一套现在几乎无处不在的敏捷实践。

回到决策树

问题 3: DevOps 还是站点可靠性工程(SRE)?

这是 DevOps/SRE 世界中最热门的辩论之一,但它可能会令人困惑,因为在许多方面,DevOps 和 SRE 非常相似。DevOps 和 SRE 都提倡类似的理念,例如持续交付、可观察性、无可指责的文化、减少管理工作,以及“开发”和“运营”团队之间更紧密的合作/联合。然而,也有一些重要的区别。

由于谷歌被广泛认为是发明了 SRE,所以尊重谷歌的定义是合适的。在这篇文章和 YouTube 视频短片中,他们将 SRE 描述为一个实现 DevOps 的类。换句话说,SRE 是一种特殊的工作实践,它实现了许多最重要的 DevOps 原则。因此,根据谷歌的说法,DevOps 和 SRE 并不冲突。

SRE 的一些关键组成部分包括 SRE(重新设想的“Ops”)团队与产品(重新设想的“Dev”)团队的分离,使用服务水平目标(SLO)来确保产品和 SRE 团队共享相同的优先级,以及使用错误预算来鼓励大量的创新和风险承担。

然而,来自 DevOps 社区的一些人对此感到不舒服。他们认为,在几乎所有情况下,由开发和运营人员健康组合而成的产品团队运行他们自己的服务要健康得多。因此,不需要集中运营或 SRE 团队。一些 DevOps 人员认为,SRE 使传统的开发和运营问题的分离正常化。他们有时认为这不可避免地会导致 DevOps 试图避免的熟悉的孤岛和功能性对峙。

问问你自己哪个感觉更真实:

  • 特定产品或服务的开发和运营问题属于不同的团队(SRE)
  • 对于拥有整个生命周期的小型跨职能团队,端到端(DevOps)

回到决策树

问题 4:小说还是教科书?

这个问题与其说是关于主题,不如说是关于学习风格:你喜欢读什么样的书?

大多数书读起来像教科书或手册。它们被分成关于不同主题的章节,它们像老师向学生解释一样解释观点,通常参考其他相关材料。

然而,有几本书是非常不同的。它们被写成了小说。他们以特定的人物为中心,这些人物通常比大多数 IT 人士所熟悉的许多刻板印象的生活讽刺要大。读者通常会在这些角色中认出他们的同事或他们自己,引发欢笑或痛苦的泪水。

大多数人认为小说更容易阅读,更令人愉快,而其他人则对通常由工程师而不是艺术专业学生写的散文感到厌恶。虽然这些故事可能出奇地熟悉,而且发人深省,但不要指望莎士比亚、奥斯汀或狄更斯那样的文学天才会深入其中。

其他人觉得教科书更容易理解。他们提出了清晰而结构良好的论点,但是,即使有世界上最好的意愿,当没有角色和情节时,人们被吸引并在情感上投入是不常见的。对大多数读者来说,它们不太吸引人。

教科书会和你的大脑对话。小说与你的直觉对话。

回到决策树

书籍:

希望到现在为止,你已经挑选了一本或多本让你感兴趣的书。点击下面的链接,阅读我对每本书的一些评论,并链接到进一步的评论和采购选项。

检查完你所选书籍的资料后,继续进入荣誉奖最终想法部分。

图书索引

  1. 目标
  2. 精益软件开发:敏捷工具包
  3. 领域驱动设计:提炼
  4. 连续交货
  5. 团队的五大功能障碍
  6. 凤凰计划
  7. 独角兽项目
  8. DevOps 手册
  9. 加速
  10. 团队拓扑
  11. 数据库可靠性工程
  12. 现场可靠性工程

1.目标(戈德拉特:1984)

The Goal (Goldratt: 1984)

事实上,这是迄今为止我所选择的最古老的书,这证明了它的古老程度以及它的重要性和永恒性。

目标是一部关于汽车零部件制造公司 UniCo 的高级经理 Alex Rogo 的小说。生意不景气。为了避免大规模重组计划和大量失业,Rogo 需要在看似不可能的最后期限之前同时提高绩效、质量和盈利能力。

他最初的努力并不顺利。他陷入了老式工厂管理的各种陷阱,专注于局部优化和成本效益。幸运的是,他遇到了一个古怪的老朋友约拿,他给了他一些奇怪的建议,这些建议似乎与他所相信的有效的工厂管理背道而驰。

该目标提供了一种真正吸引人的、个性化的、可及的、实用的方法来学习基本的精益原则,如价值流、流程、浪费、全球效率和约束理论。

与我列表中的其他书不同,这个目标几乎没有提到软件。毕竟,这是一本关于制造而不是软件开发的书。这是一件好事,因为它有助于读者理解原始上下文中的基本精益原则,这意味着读者不需要任何 it 经验就能理解这些概念。

如果你想了解更多关于这些精益原则如何应用于现代软件开发的信息,我鼓励你阅读凤凰计划(一个基于 IT 部门的目标的现代复述)或者精益软件开发:一个敏捷工具包(一本更正式的教科书)。

更多评论,以及采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

2.精益软件开发:敏捷工具包(Poppendieck,Poppendieck: 2003)

Lean Software Development: An Agile Toolkit (Poppendieck, Poppendieck: 2003)

一本经得起时间考验的书。

我不想猜测有多少软件书籍的标题中有“敏捷”。可以说,“敏捷”是软件最初的营销术语。这可能与一次酒后滑雪旅行有关,一群人创建了一个简洁的网站。

然而,撇开怀疑不谈,我相信这些时髦的词语非常适合销售软件、培训和认证,因为它们背后有一个基本的真理。这些想法在技术和商业上都很有意义。

我可能在上面嘲笑过它,但是敏捷宣言有它的位置。对许多人来说,这是阐明敏捷的重要垫脚石。然而,对敏捷更好的表述是玛丽和汤姆·波彭迪克的《精益软件开发:敏捷工具包》,该书于两年后出版。

Poppendiecks 采用了 7 个核心精益原则,并实际讨论了它们如何从制造领域转化为软件开发:

  1. 消除浪费
  2. 加强学习
  3. 尽可能晚地决定
  4. 尽可能快地交付
  5. 授权给团队
  6. 在...中建立诚信
  7. 看到整体

一路上,他们涵盖了 20 种将精益原则应用到实践中的工具,例如价值流图、迭代工作和重构。

这本书是对敏捷实际上是什么,它如何建立在稳定的精益基础上,以及为什么特定的实践会产生更好的软件交付结果的最好的阐述之一。作为一本“敏捷”的书,它在技术上不是一本“DevOps”的书(它是在 Patrick DeBois 偶然给了我们那个时髦词的 6 年前写的),但是这本书是 DevOps 起源中的一个重要章节。

对于那些想在最初的制造环境中了解更多精益原则的人,请查看目标。对于那些想了解这些敏捷思想是如何随着 DevOps 的出现而发展的人来说,看看的 DevOps 手册。对于那些想深入软件设计/架构的人,试试领域驱动设计:精华。最后,对于那些对敏捷/开发工作人员更感兴趣的人来说,你可能更喜欢团队的五大功能(更个人化)或团队拓扑(更具战略性)。

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

3.领域驱动的设计精华(Vernon: 2016)

Domain-Driven Design Distilled (Vernon: 2016)

领域驱动设计(DDD)是一种方法,它使人们能够创建松散耦合的架构,并避免创建一个“泥巴大球”式的整体系统,这对于开发、部署或维护来说是痛苦的。Martin Fowler 在他的博客上对此做了更好的描述,比我在这里的几个段落中所能合理预期的要好。

DDD 的中心思想是“有界环境”,它可以用来定义系统的任何一部分的范围。团队成员承认,在任何一个上下文中使用的语言都是一致的,但在不同的上下文中也可能有所不同。这使得团队可以专注于在正确的地方解决正确的问题,并在不同的上下文之间建立适当的接口,以避免复杂的依赖性和由于微妙的上下文差异而引入的错误。再次,Fowler 在这里更详细地解释了有界环境。

写这样一篇文章的最大好处是收到反馈。当我在整理我的书目列表时,我向 Twitter 征求反馈,一些人推荐了一本关于 DDD 的书。例如:

老实说,这是我发现了自己的盲点。DDD 不是我以前研究过的话题,我也没有意识到 DDD 和德沃普斯之间的关系。我感谢所有鼓励我去看一看的人。现在我的书架上有一本新书,我也一直在学习。

大多数人推荐 Eric Evans 的领域驱动设计,但是 Matthew Skelton(《T2 团队拓扑》的作者)也向我推荐了 Vernon 的“精选”版。经过一点研究,我了解到埃文斯的书有很好的评论,是公认的 DDD 的黄金标准。然而,它也被认为是漫长而复杂的。(它有 560 页长,售价超过 50 美元,比本文中的任何一本书都要长,也更贵。)

因为这是我第一本关于 DDD 的书,所以我买了一本弗农的“精华”版。只有一半的价格和 130 页丰富的图表。即使是阅读速度慢的人(像我一样)也可能在几个小时内从头到尾读完。我把弗农的书列入我的清单,因为我不愿意推荐一本我没读过的书,但我认识到许多读者可能更喜欢直接去读埃文斯的书。

Vernon 这本书是一本很好的关于 DDD 的入门书,对任何有一点软件开发经验的人来说都相对容易理解。对于那些正在与一个单一系统作斗争的人,或者那些希望避免他们的东西逐渐变成一个系统的人来说,这尤其重要。

在阅读完《领域驱动设计精粹》之后,你可能会想通过上面提到的 Evans 的书更深入地了解 DDD。或者,您可能想阅读更多关于松耦合架构如何更容易使用的内容。这在某种程度上在加速devo PS 手册中都有涉及。

你可能还想看看 Sam Newman 的构建微服务(在这篇文章中有一点遗漏了),在站点可靠性工程中,你将了解 Google 如何维护一个拥有许多松散耦合服务的大型复杂环境。

最后,我鼓励你看一看团队拓扑。软件架构从团队架构开始,团队拓扑使用康威法则将有界上下文和松散耦合系统的思想应用到我们在 IT 部门构建团队的方式中。

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

4.持续交付(Humble,Farley: 2011)

Continuous Delivery (Humble, Farley: 2011)

我第一次参加伦敦连续交付(CD) Meetup 小组时,这本书刚出版不久,一半的与会者都拿着一本。他们称之为“圣经”。虽然 DevOps 运动在 2011 年就已经开始了,但我想我还没有听说过它。我们的社区有一个非常相似的东西,叫做 CD。

这本书正式提出了许多关于部署管道的想法,这些想法后来变得无处不在。它深入探讨了一些关于配置即代码、构建和部署自动化以及有效测试策略的技术细节。

大多数人不倾向于从头到尾阅读这本书。(挺干的,技术含量高。)相反,它最好用作参考书。如果您打算采用新的技术实践,那么在开始之前有必要阅读相关章节,以帮助您理解如何实现它。

这本书通常非常专业,主要关注部署管道的实际实现。然而,它也讨论了诸如移交、快速迭代、实验和学习等主题。在许多方面,一旦你开始通过这个更广阔的镜头来看 CD,就很难区分 CD 和 DevOps。

事实上,今天,对裁谈会的范围有些混乱。对某些人来说,这只是关于部署管道的实际实现。对其他人来说,CD 包含更广泛的文化和组织问题。有些人甚至会说 DevOps 是 CD 的一部分,而不是相反。

例如,两位作者现在似乎站在了这场辩论的对立面。Jez Humble 接着与人合著了 Accelerate ,该书的封面上有“DevOps”一词,并在“架构”、“产品和流程”、“精益管理和监控”以及“文化”的标题下描述了一组与其他功能截然不同的“连续交付”功能。与此同时,戴夫·法利将 CD 视为 DevOps 的同义词(过去,他声称 DevOps 只是 CD 的一个组成部分),正如在他的 YouTube 频道上表达的那样。

就我个人而言,我厌倦了这场辩论。CD 和 DevOps 是一起成长起来的,并且是从类似的精益、极限编程和敏捷思想中衍生出来的。通过独立思考和借鉴,他们得出了相似的结论。我鼓励你庆祝这两个运动相互支持的方式,而不是停留在它们之间的关系上。(我希望 Jez 和 Dave 会同意。)

如果你喜欢连续交付,你可能也会喜欢现场可靠性工程。这是一本类似的技术书籍,专为大型 It 部门设计,涵盖了更多生产/运营/维护方面的其他主题。另一方面,如果你觉得 CD 有点枯燥,想要一些更容易理解的东西,试试的 DevOps 手册的 Accelerate 吧。

更多评论,以及采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

5.团队的五大功能障碍(Lencioni: 2002)

The Five Dysfunctions of a Team (Lencioni: 2002)

Andrew Clay Shafer 在 2009 年的“困惑之墙”幻灯片引起了共鸣,因为它很好地表达了开发和运营之间的“核心长期冲突”。引起了共鸣。痛苦地。

大多数大型组织都有比简单的“开发”和“运营”更复杂的政治结构,很难让所有的职能部门协调一致地工作——然而这恰恰是实现短交付周期、频繁发布和“流动”所需要的。

只有团队合作才有可能达成一致,而没有训练有素的团队成员,团队合作是不可能的。正如杰拉尔德·温伯格的名言,“不管他们告诉你什么,这总是一个人的问题”。

这也是 DevOps 社区一直以来都非常关注人和文化的原因。然而,技术人员通常更愿意谈论软件或自动化,而不是情感、信任或个人弱点。这导致了 DevOps 的一个问题:太多时候,它归结为简单的自动化部署、作为代码的基础设施或最新的供应商工具。这些过度简化完全没有抓住要点。

团队的五大功能障碍为解决“人的问题”提供了一个逻辑框架。前 180 页讲了一个“领导力寓言”,类似于目标凤凰计划,或者独角兽计划。DecisionTech 是一家具备所有成功要素的初创公司,但高层领导团队却是一团混乱。新任首席执行官凯瑟琳·彼得森(Kathryn Peterson)的任务是团结这些才华横溢但狡猾谨慎的个人,扭转公司的命运。

最后 40 页正式回顾了凯瑟琳用来创建一个有效团队的框架。你可以跳过这个寓言,只阅读这 40 页,然后在一个小时内完成,但这就像在沉默中阅读乐谱,或者通过阅读文档而不安装它来学习使用一些新技术。为了得到真正的赞赏,阅读寓言并观察框架的运行是很有价值的。

如果你最大的问题是“人的问题”,这本书应该是你首先要读的书之一。

读完“功能障碍”之后,如果你想学习如何在你的组织中构建多个团队以取得成功,你可能会想尝试阅读团队拓扑。如果你喜欢另一个寓言,看看的凤凰计划,其中一些“功能障碍”的练习被用来在项目管理灾难中把敌对的领导团队聚集在一起。

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

6.凤凰计划(金,贝尔,斯帕福德:2013 年)

The Phoenix Project (Kim, Behr, Spafford: 2013)

早些时候,我提到其中一本书改变了我的职业生涯。就是这个。

凤凰计划是对你曾经工作过的每一家公司的讽刺。它充满了你过去经常合作的传奇人物。有脾气暴躁的数据库管理员,了解所有最关键系统如何工作的孤独工程师,不了解技术的高级领导人物,以及许多其他人。你看了这本书,认出了你的同事。一个接一个,你对自己傻笑。戴夫在那儿。那是苏珊。哦,等等,那是我。

当 Gene Kim 读到目标时,他受到了极大的鼓舞,以至于他想在软件开发的背景下复述这个目标。凤凰计划是一种敬意。亚历克斯·罗戈被比尔·帕尔默取代,他是一名中层经理,在前任被解雇后,无意中被提升为高级领导。他在另一家汽车零部件制造公司 Parts Unlimited 工作,生意不景气。听起来熟悉吗?

Parts Unlimited 将赌注押在了凤凰计划(Phoenix Project)上,这是一个旨在扭转公司命运的大型新软件版本。他们向投资者承诺将在几周内交付,但团队已经无望地落后于计划,营销部门通过各种非官方的秘密渠道不断增加工作量。与此同时,频繁的灭火正在从凤凰城抽走资源,并导致更多的延误。这是一个恶性循环,从中逃脱似乎是不可能的。

如果 Phoenix 不能按时交付,Bill 知道他和他的大多数同事都会失业。

《Goal》中的乔纳被埃里克取代,他是一个古怪的新投资者,有着挑战比尔传统智慧的疯狂想法。在 Eric 的指导下,Bill 认识到了他关于如何管理 IT 项目的旧观念中的缺陷。他踏上了理解“三种方式”的旅程:心流、反馈和持续的实验和学习。

在这个过程中,每个原型人物要么学会接受一种新的工作方式,要么得到应有的惩罚。当我意识到我所认识的角色不会有好的结局时,我个人知道我必须改变。

在我看来,这本书最有价值的地方在于,当我认为自己的工作做得很好的时候,它很好地阐述了我正在做的伤害。凤凰计划帮助我在一个更广阔和更有价值的背景下理解我的角色和我行为的后果。如果不是这本书,我可能不会写这个帖子。

如果你喜欢凤凰计划,你可能也会喜欢独角兽计划,它从一个非常不同的角度讲述了同样的故事,你可能会有兴趣回过头来阅读《T2》的目标。如果你想了解更多关于建立优秀团队的知识,请查看团队的五大功能障碍。如果你想对菲尼克斯提出的主题有一个更正式的概述,可以试试 DevOps 手册或 Accelerate。

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

7.独角兽项目(金:2019)

The Unicorn Project (Kim: 2019)

独角兽计划是凤凰计划的重演。既不是前传,也不是续集。同样的故事,在同样的时间线上,但是从不同的角度讲述。

凤凰城项目是从比尔·帕尔默的角度讲述的,作为一名具有业务运营背景的高级经理。Phoenix 谈了很多关于精益原则和协作以及所有有价值的东西,但是 Bill 已经被提升得足够远了,他很少再玩代码了,而且他缺乏开发经验。这可能使 Phoenix 项目成为开发人员不满意的读物,有时给他们一种印象,除非你在高级管理层,否则你不能对底层问题做太多。

相比之下,独角兽项目讲述的是玛克辛·钱伯斯的故事。她是一名高级开发人员,在因为一场不是她的错的灾难而受到指责后,刚刚被降级到凤凰项目。她深受团队的尊敬,是一个强有力的领导者,因为她热爱编码,所以她回避正式的管理职位。

除了一些组织主题,如公司官僚主义、责备文化和冗长的批准过程,“Unicorn”还涵盖了更多的技术主题,包括持续集成(CI)、数据管理和函数式编程。(是啊,我承认最后那个我有点惊讶!)

Unicorn 的主要焦点是“五个理想”:

  1. 地方性和简单性
  2. 专注、心流和快乐
  3. 日常工作的改进
  4. 心理安全
  5. 客户导向

虽然 Unicorn 无疑是针对工程师而不是经理,开发者而不是运营,但我认为 Phoenix 和 Unicorn 的结合非常有价值。坦率地说,如果你喜欢一个,你可能会喜欢另一个,从两个不同的角度阅读同一个故事很有启发性。这是一个很好的练习,让你学会理解和同情对方看待事物的方式。

如果你喜欢独角兽计划,试着阅读下面的凤凰计划。如果你想了解更多关于建立优秀团队的知识,请查看团队的五大功能障碍。如果你想了解更多关于为 DevOps 设计代码的知识,试试领域驱动设计:精华,或者如果你是数据专家,试试数据库可靠性工程。最后,如果您想了解更多关于持续集成和部署管道的信息,请查阅持续交付devo PS 手册

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

8.《DevOps 手册》( Kim,Humble,Debois,Willis: 2016 年)

The DevOps Handbook (Kim, Humble, Debois, Willis: 2016)

这本书也许是对 DevOps 的最好诠释。它的目的是作为凤凰计划的伙伴,它的目标是将凤凰的思想和模式编纂成一个更加正式和可操作的袖珍手册。(虽然你需要相当大的口袋。)

它以约翰·威利斯的一篇名为《DevOps 的融合》的文章开头。这是一篇短小精悍的文章,描述了各种相互关联的运动(精益、敏捷宣言、敏捷基础设施、持续交付、丰田 Kata)是如何在几乎相同的时间得出相似的结论,以及它们是如何汇聚在 DevOps 的旗帜下的。

然后,该手册使用 Kim 在凤凰计划中提出的“三种方法”作为逻辑结构,通过这种逻辑结构,它阐述了 DevOps 核心的许多关键思想和实践,包括价值流图、部署实践、测试、遥测、实验/学习。还有一个专门讨论安全性和合规性的部分。

写得也很好。通过参考其他伟大的书籍、文章和视频,这很容易理解。它还包括大量的现实世界的案例研究,以证明在实践中的理论。

DevOps 手册相对于连续交付 (CD)或现场可靠性工程 (SRE)的关键区别之一是其可访问性。它用简单明了且符合逻辑的术语解释了想法和概念,并将它们直接与商业价值联系起来,即使不是经验丰富的工程师或商业领袖的人也相对容易理解。这就是为什么它是缺乏近期技术经验的 it 经理或高层领导的好读物的一个原因。

这种易接近性也可能是它的缺点。在我已经熟悉的话题上,我想更深入,但我留下了疑问。

“你应该如何处理这种不寻常的情况?”

“这在实践中如何运作?”

例如,如果与 CD 或 SRE 相比,DevOps 手册不太详细,但范围更广,因此更容易阅读。不像 CD 或 SRE(相当密集),你可能会发现自己从头到尾阅读手册。

DevOps 手册是 DevOps 新手或想更好地理解 DevOps 各部分如何结合的人的绝佳读物。读完之后,你很有希望受到启发,继续深入你最感兴趣的话题。

如果你还没有读过凤凰计划,它是 DevOps 手册的好伙伴,因为它讲述了一个 it 经理将“三种方法”付诸实践的故事。如果你想更深入地了解任何特定的主题,你可能会在《手册》中找到大量的参考资料。事实上,本帖中的大部分书籍都是在手册出版之前的,在某些时候会被引用。

如果您是高级管理人员,并且您对更多阅读材料感兴趣,以帮助您构建成功的 IT 组织,我建议您看一看 Accelerate ,它采用科学的、数据驱动的方法来分析来自 DevOps 报告的数据,以提供一个实证案例,说明为什么手册中的许多实践能够带来业务价值。您可能还会喜欢团队拓扑,它探索了各种团队结构,这些结构或者促进或者抑制流动。

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

9.加速(福斯格伦、亨布尔、金:2018)

Accelerate (Forsgren, Humble, Kim: 2018)

如果说DevOps 手册解释了“如何”采用 devo PS,Accelerate 解释了“为什么”。对于那些对 DevOps 持怀疑态度或者对理解各种 DevOps 实践和商业成功之间的关系感兴趣的人来说,这是一个强有力的论据。这是向高级管理层推销 DevOps 的绝佳工具。

在许多方面,这本书是对“DevOps”是一个时髦词或邪教的批评的回应,充满了温暖而松散的想法,这些想法听起来很好,对小企业或初创企业来说很有意义,但实际上无法扩展到大型组织,或者与医疗保健或金融等受到严格监管的行业不兼容。

作者通过分析 2014-2017 年devo PS 报告的数据实现了这一点。Forsgren,一位备受尊敬并发表论文的研究人员,将各种科学和统计方法应用于这些数据,看看它们能教给我们什么。她的发现是惊人的。

长话短说:DevOps 的商业利益是真实的和可预测的。

尽管《Accelerate》采用了非常科学和统计数据驱动的方法,但它是一本相对简短且易于理解的书,用简单的英语编写,其格式应该对任何企业中的技术人员或商业人员都有意义。

这本书分为三个部分,每个部分都很不同。

第 1 部分开门见山,用短短的 130 页,丰富的图表,详细介绍了非凡的发现。坦率地说,如果你是 DevOps 的新手,这 130 页将会是一本很好的 DevOps 入门书,即使你后来决定转到这个列表中的其他书。

就我个人而言,我是有声读物的粉丝,随着时间的推移,我逐渐提高了播放速度。我现在以大约两倍的速度听它们,任何慢一点的都感觉乏味。在前 covid 时代,我经常出差。我在不到 90 分钟的时间里听完了 Accelerate part 1 的全部内容,当时我正从一个“DevOps Health Check”式的咨询项目开车回家。这本书描述了这个客户和我的许多其他客户正在努力解决的许多问题。

我被我所听到的迷住了,一回到家,我就为自己订购了一份硬拷贝,以便第二天交付,这样我就可以更仔细地重读它。

Accelerate 帮助我了解和理解了我经常与客户一起遇到的许多问题。以前,我会凭直觉认为某些做法是有害的,但我努力权威地阐明原因。Accelerate 给了我向技术和商业利益相关者解释我的直觉反应的语言和证据。

虽然我发现第 1 部分非常有用,但我从未试图阅读第 2 部分。我不认为它是为我设计的。这是一场学术讨论,讨论的是用于得出第一部分中详细结论的科学和统计方法。我确信,对于任何对这些方法感兴趣的统计或数据爱好者,或者任何想找出逻辑缺陷的怀疑论者来说,这都是有价值的阅读材料。就我个人而言,尽管我是数据库专家,但我不确定我属于哪一类。

我喜欢驾驶,也喜欢编码,但我不会设计汽化器或中央处理器。同样,我认识到我更感兴趣的是应用 Accelerate 教授的课程,而不是理解用来提出这些课程的科学和统计模型。对我来说,重要的是作者的方法有效。鉴于这本书的受欢迎程度以及我没有听到对作者方法的任何严肃批评,我个人的结论是这些方法可能是可靠的。(如果他们不是,我也不太可能成为挑战他们的人。我承认妮可·福斯格伦比我更擅长研究和分析。)

尽管没有阅读第 2 部分,但我确实发现第 3 部分很有用。这是荷兰国际集团的一个案例研究。它讲述了一个应用了第 1 部分中的经验教训的特定组织的故事。作者给出了明确的警告,重要的是 ing 解释这些教训的方法,而不是他们实施的特定实践。这是完成这本书的一个有用的方法,因为它展示了如何将理论付诸实践。

那么有什么教训呢?

Accelerate 高度重视针对四个关键指标的同步和持续改进:

  1. 研制周期
  2. 部署频率
  3. 平均恢复时间(MTTR)
  4. 部署失败百分比

这四个指标相辅相成,形成良性循环,并通过盈利能力、市场份额和生产力来预测企业的成功。

为了实现业务成功,Accelerate 强调了 24 项实践能力(例如自动化部署和支持学习),这些能力被证明可以推动针对四项指标的改进。

Accelerate 提供了可靠的证据和推理,这解释了为什么 DevOps 作为一种交付巨大商业价值的方法。这使得它成为一个伟大的第一 DevOps 书或一个缺乏技术经验的高级领导团队的伟大礼物。例如,如果你读过凤凰计划,想象一下,如果比尔·帕尔默(IT 运营副总裁)成功说服史蒂夫·马斯特斯(首席执行官)阅读第一章中的《加速》,他将会多么悲伤。

如果你的角色是一个技术角色,或者你是一个负责技术成果的高级经理,在读完 Accelerate 之后,你可能想继续看一本更实用的“如何做 DevOps”的书,像的 DevOps 手册连续交付站点可靠性工程领域驱动设计:精华或者(如果你需要在解决技术问题之前解决人员、团队或文化问题)团队的五大功能障碍或者

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

10.团队拓扑(Skelton,Pais: 2019)

Team Topologies (Skelton, Pais: 2019)

团队拓扑使用康威定律来弥合关于设计有效软件架构的书籍(例如领域驱动设计:提炼)和关于创建有效团队的书籍(例如团队的五个功能障碍)之间的分歧,同时优化价值到最终用户的快速“流动”。

马修·斯凯尔顿(Matthew Skelton)深受英国持续交付运动的影响,这种影响闪耀着光芒。我个人知道这一点,因为在 2017 年的大约一年时间里,我和他共同组织了伦敦连续交付会议小组。与会者有时会夹着一本连续交付——他们称之为“圣经”。斯凯尔顿主持的衍生管道会议,是我参加/支持过的最受欢迎、最多样化、最有启发性的科技会议。

《团队拓扑》是一本书,面向 IT 部门的高级经理。它认为软件体系结构从团队体系结构开始,并且它提供了一个设计你的团队的蓝图,以及他们之间的通信/协作协议,以这样一种方式,你喜欢的软件体系结构自然地出现。这从根本上减少了对有毒官僚机构的需求和糟糕的架构选择的风险,而糟糕的架构选择会累积大量的技术债务。

如果您喜欢团队拓扑,并且希望了解更多关于它所讨论的一些核心概念,那么上面前两段中提到的三本书是一个很好的开始。你可能也会喜欢 Accelerate ,这是另一本适合高级 IT 领导的理想书籍,它使用科学的、数据驱动的方法将各种技术和文化实践映射到业务成功。

更多评论,以及采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

11.数据库可靠性工程(坎贝尔,专业:2018)

“这本书绝对有价值:这是 250 页的谷歌网站可靠性工程书(我喜欢这本书)的概念版本,面向那些目前可能自称为数据库管理员,但想去快节奏、大规模公司工作的人。”

那些不是我说的话;它们是 Brent Ozar 在他自己对《DRE》的评论中写的。

Database Reliability Engineering (Campbell, Majors : 2018)

当我在编辑这篇文章时,我看到了布伦特的评论,坦白地说,我很喜欢它。他说的正是我想说的,但更好。他解释了读者应该如何阅读这本书,这取决于他们是 DBA、经理还是开发人员/系统管理员。

就我个人而言,我最喜欢从 DRE 学到的是对“弹性重于健壮性”的关注,以及无处不在地使用 SLO 作为一种方法来调整目标并改进开发和运营之间跨越“混乱之墙”的协作。我还欣赏对“操作可见性”的关注,而不是传统的监控,这是当前#可观察性辩论的核心。

布伦特的结束语是:

“这本书很容易读懂,但很难实施。说真的,仅仅是实施第 2 章中描述的 SLO,就需要大多数传统公司花上几个月的时间来达成一致并进行监控。

“随着时间的推移,品牌名称和开源工具会发生变化,但这些概念至少在十年内会坚如磐石。对于我们大多数人来说,这本书是一个很好的路标,设定了未来 5-10 年的时间,但它会让你兴奋地为之努力。”

在这里查看 Brent 的完整评论(不过看完请回来)。

这差不多概括了“DRE”。谢谢你,布伦特,为我节省了一点时间!

如果你喜欢“DRE”,很可能你也会喜欢它的姐姐,现场可靠性工程,它的规模更大,范围更广。然而,如果你喜欢稍微轻松一点的东西,你可能会喜欢凤凰计划,在这个项目中,运营团队学会更有效地与开发人员和业务的其他部分合作,以避免重复的以数据为中心的灾难。你可能还会喜欢 Phoenix 的姊妹小说《独角兽计划》,它从一个继承了一个单一数据库的高级开发人员的角度讲述了同样的故事,并且有效地承担了创建和维护数据湖的任务。

如果您正在努力将一个整体数据库分解成一组更小的、更松散耦合的数据库,那么阅读域驱动设计:精华也会让您受益。最后,如果您是一名老派的 DBA,并且您对 DevOps 仍然持怀疑态度,我鼓励您看一看 Accelerate

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

12.现场可靠性工程(拜尔、琼斯、佩托夫、墨菲:2016 年)

The Phoenix Project (Kim, Behr, Spafford: 2013)

你已经找到了这本书里最重的一本书。

网站可靠性工程(SRE)详细介绍了谷歌如何持续交付和维护其庞大的产品组合。它在 DevOps 社区中如此受欢迎和有影响力,以至于现在有一个越来越多的子社区将“SRE”作为一个独特的事物,或者在“DevOps”内部。

阅读这本书是一项合理的时间投资,所以对于那些刚到 SRE 的人来说,我建议你在购买之前,先浏览以下链接:

  1. 谷歌的这篇博客文章: SRE vs DevOps:竞争的标准还是亲密的朋友?
  2. Thomas A. Limoncelli 在纽约 USENIX 的 2012 年会议:SRE @谷歌:自 2004 年以来成千上万的 DevOps】

这 10 分钟的阅读/ 45 分钟的观看应该会让你对谷歌的方法有一个很好的了解,涵盖了重要的主题,包括 DevOps 和 SRE 之间的差异,以及 SRE 的一些核心组件,包括 SLO、错误预算和客户可靠性工程。

然后你有 560 页,涵盖 34 个章节,详细介绍了谷歌用来管理其服务可靠性的一些技术和文化实践。这里面有太多的东西,我甚至不打算进一步探讨任何具体的技术。

尽管《SRE》是一本引人入胜的读物,但值得记住的是,我们大多数人并没有为接近“谷歌规模”的组织工作。考虑到这一点,SRE 与在超大型组织工作的人最为相关。

对 SRE 的批评之一是,它有效地规范和倡导了一个单独的 SRE (Ops/DevOps)团队的存在。我个人的观点是,DevOps 社区的大多数人认为,在绝大多数情况下,当然是在大多数中小型公司中,团队拥有其产品或服务的端到端生命周期比将“运营”问题交给单独的筒仓更好。

除了这一点,书中的许多实践可能具有广泛的适用性和相关性。它可能会给你一些如何解决你自己的挑战的想法。

《SRE》是一个很好的读物,适合那些自认为是 DevOps 系列中“Ops”端的人,也适合那些想方设法改善不同开发和 Ops 团队之间关系的人。对于那些想深入了解数据和数据库的人来说,也可以看看数据库可靠性工程。对于那些对连续交付挑战而不是可操作性挑战更感兴趣的人来说,可以考虑连续交付。对于那些想了解更多关于将单块系统分割成一个更松散耦合的服务集合的人来说,看看领域驱动设计:精华。对于那些喜欢 SRE 但不明白它与 DevOps 有何不同的人来说,给自己买一本《DevOps 手册》作为比较(具体来说,仔细看看第 7 章)。对于那些犹豫是否要建立一个独特的 SRE 团队的人,你可以在团队拓扑中找到一些指导。

更多评论,以及预览和采购选项:在 GoodReads 上查看这本书

上一页:书籍索引 /下一页:优秀奖

荣誉奖

上个月,我在 Twitter 上发布了我第一次尝试汇编这个列表的结果:

我很幸运地收到了一些精彩的反馈。总的来说,这是非常积极的:

然而,这都是非常主观的。这是一个大而多样的社区,有很多意见。有很多人建议我增加这本书或者删除那本书。我听取了建议,也做了一些改变。尤其是像安德鲁·克莱·谢弗这样的人给了我直接的建议。(对于不知道他是谁的人,姑且说他的 Twitter 手柄有点谦虚。当我看到他的回复时,我感觉自己完全是个追星族。)

你可能会喜欢浏览各种 Twitter 帖子,看看其他人喜欢和不喜欢哪些书,我也希望你能回复自己的评论。(尤其是如果这篇帖子激发了你去读一些东西的话!)

还有一些不是传统书籍的读物。因此,它们并不完全符合上面的决策树。尽管如此,它们还是很值得你关注的。

DevOps 年度状态报告

自 2012 年以来,来自 Puppet 和 DORA 的研究人员团队在各种赞助商的支持下,对数千名 IT 专业人士进行了民意调查,并分析了结果。最初由岚娜·布朗领导,后来由妮可·福斯格伦领导,该研究项目为 DevOps 运动提供了更多数据驱动、基于证据的基础,这是任何单个作者或演讲者分享其个人经历所无法实现的。

Forsgren、Humble 和 Kim 使用 2014-2017 年报告中的数据制作了 2018 年的 Accelerate ,我认为这仍然是我们行业拥有的关于 DevOps 价值的最佳证据。

你可以在这里查看所有的 DORA State of DevOps 报告(2014-2019,撰写本文时)。

超越凤凰计划

超越凤凰计划从技术上来说是一本有声读物,但它更像是一个播客系列。这基本上是吉恩·金(凤凰计划的合著者,以及这篇文章中的其他标题)和约翰·威利斯(开发人员手册的合著者)之间 7 个小时的谈话记录。

金和威利斯开始探索 DevOps 的历史,从其最早的起源(威利斯声称可以追溯到 1859 年查尔斯达尔文!),他们一路挖掘各种有影响力的人物的生活和教义,包括威廉·戴明、大野泰一和埃利耶胡·戈德拉特。

然后,他们回顾了现代 DevOps 的一些最重要的理论基础,包括精益制造和安全文化,然后实际查看了凤凰项目中的各种 DevOps 想法。

他们以 2017 年 DevOps 企业峰会活动的一段录音结束,在那里,他们召集了一群专家来探索精益和安全文化之间的共性。

从头到尾,这是一次迷人的聆听,许多人会一遍又一遍地重复。

你可以在这里购买超越凤凰计划。

编辑:自从这篇文章首次发表后,我了解到你可以在这里买到对话的文字记录。

复杂系统如何失败

当探索 DevOps 的历史基础时,大多数人(包括我自己)立即跳到精益制造,这是很有道理的。然而,许多人没有认识到 DevOps 在很大程度上也归功于“安全文化”或“安全 2.0”运动,它有着完全独立和非常不同的历史。可以说,诸如敏捷基础设施、混沌工程、弹性高于稳健性、可观察性和无过失文化等理念更多地归功于安全文化,而不是精益制造。

安全文化是对复杂且经常是高风险环境中的灾难原因的研究。在 IT 中,我们经常陷入这样一个陷阱,认为我们的常规 IT 故障应该将我们的工作归类为高风险。这通常是一个错误,大多数认为这不是错误的人显然从未在军队、医院或航空业工作过,在这些行业,人们每天都要做出生死抉择。我们的部署失误通常比士兵、外科医生或飞行员的失误重要得多。这常常值得提醒我们自己。(想象一下,如果有人真的死了,进行无可指责的验尸会有多困难。)

然而,我们可以从那些真正必须每天管理严重风险的人身上学到很多东西。20 世纪 90 年代,Richard Cook 在卫生保健部门工作,研究病人安全。1998 年,他发表了一篇名为《复杂系统如何失败》的论文。这是一本相对简单易懂的书,总结了 18 个有效管理风险的核心理念。疯狂的是,作为一名 IT 人员,如果你不知道他写的是关于医院安全的文章,你可能会认为你正在阅读。

通过思考 Cook 的研究并想象对一个采用 DevOps 的虚构 it 组织和另一个支持老式瀑布式项目管理和官僚主义的 IT 组织的影响,很容易理解为什么瀑布式组织更有可能遭受更频繁和更严重的灾难。

我不会试图在这里总结安全文化的要点,因为这将花费太多时间,最终,我只是在解释库克为自己清楚表达的相同观点。我鼓励你读报纸;只有 10 分钟的阅读:https://how.complexsystems.fail/

其他著名的 DevOps 阅读书目

不要只相信我的话。我只是一个对 DevOps 着迷的不完美的人,尽我所能分享一些引起我共鸣的书籍。如果你想看其他著名来源的汇编,看看下面的列表。你会在这些列表中看到许多熟悉的标题,以及一些不同的标题。

最后的想法

不是每本书都适合所有人。比如很多人爱凤凰计划,但也有人不喜欢这个格式。我意识到我决定加入一本关于数据库的专业书籍反映了我的个人经历。为什么不写一本关于看板/scrum、测试、产品管理、混沌工程或安全的书呢?这些都是公平的问题。

这类帖子通常很快就会过时,这也是事实。我在 2020 年 10 月写这个帖子,上面的标题有一半是 2016 年以来发表的。我确信,在接下来的几年里,新书会被写出来,旧书会被重新发现,现在流行的书会过时。我们使用的技术在不断发展,DevOps 社区对学习和改进有着深厚的热情,所以如果未来几年没有发展,那将是一个惊喜和遗憾。

考虑到这些,亲爱的读者,我想让你反败为胜。你喜欢决策树吗?你喜欢还是不喜欢我单子上的书?你的个人书籍推荐合适吗?你读了吗,它有价值吗?有没有我忽略的书,你觉得应该被剔除?你的 DevOps 阅读清单会是什么样的?

请留言评论。期待看到大家的建议,也希望能在我的个人阅读清单上增加几条。

但更重要的是,如果你喜欢书,我鼓励你买一本,并留出一些时间开始阅读。在这个比以往任何时候都更加疯狂的时刻,我发现留出几个小时,打开水壶,关掉设备,拿着记事本、笔和一本关于如何在面对意想不到的挑战和不确定的市场条件时取得成功的好书坐下来,这是特别有益的。

阅读我们的 Runbooks 系列的其余部分或探索 DevOps 工程师手册以了解关于 DevOps 和 CI/CD 的更多信息。

愉快的部署!

DevOps、runbooks 和 kubectl - Octopus 部署

原文:https://octopus.com/blog/devops-runbooks-and-kubectl

可以肯定地说,开发人员不应该学习 Docker、K8s 或 30 种其他东西来部署一个应用是我们许多人都同意的观点。

实话实说吧,Kubernetes 并不容易。但是有办法让支持它不那么痛苦。Octopus 中的 Runbook 功能允许您编写多年来为 Octopus 部署提供动力的相同流程的脚本,以管理整个环境中的日常维护和紧急操作(事件响应)任务,而无需创建部署。

在这篇博文中,我们看一个简单的操作手册,并强调创建可重用操作手册相对于手动脚本和专门调试的优势。

Octopus 2021 Q3 包括对 Kubernetes 部署的更新支持,以及针对 Google Cloud、AWS 和 Azure 用户的 runbooks。在我们的发布公告中了解更多信息。

一个简单的 Kubernetes runbook 示例

在支持 Kubernetes 集群时,列出 Kubernetes pods 以查看其状态是常见的第一步。这听起来很简单,很容易认为这个过程只不过是运行kubectl get pods。但是有了操作手册,就有可能丰富这个诊断过程。

下面是一个 Get Pods 脚本的示例,您可以将其创建为操作手册的一部分:

$arguments = @("get", "pods")

if ($Format -ieq "YAML") {$arguments += @("-o", "yaml")}
if ($Format -ieq "JSON") {$arguments += @("-o", "json")}

$result = & Start-Process `
    -FilePath 'kubectl.exe' `
    -ArgumentList $arguments `
    -NoNewWindow `
    -Wait `
    -RedirectStandardOutput "result.txt"

Get-Content "result.txt"

New-OctopusArtifact "result.txt"

# Add some intelligence to guide the troubleshooter
$notRunning = & kubectl get pods -o json |
    ConvertFrom-JSON |
    Select -ExpandProperty items |
    Where-Object {$_.status.phase -ne "Running"}

if ($notRunning.Count -ne 0) {
    Write-Host "The following pods are not in a Running state. These are worth investigating further."
    $notRunning |
    ForEach-Object {Write-Host "$($_.metadata.name) is not running."}
} 

我们通过构建一个要传递给kubectl的参数数组来开始这个脚本。基本参数是get pods,但是根据名为Format的提示变量的值,我们也可以在 JSON 或 YAML 中获得 pod 的详细信息。对kubectl调用的响应被保存到一个名为result.txt的文件中。

New-OctopusArtifact Cmdlet 获取文件并将其保存为 Octopus 工件,在流程完成后,可以从任务摘要中获得该工件。

Screenshot of Task Summary page in Octopus showing Step 1 Get Pods

然后,脚本再次调用kubectl,这一次寻找不处于运行状态的 pod。输出中会列出找到的任何 pod。

这个脚本并不特别复杂或巧妙,但是它强调了使用 runbooks 的许多好处。

至少,与 Kubernetes 集群交互需要安装kubectl命令行工具。根据集群的不同,您可能还需要额外的身份验证可执行文件。

根据我的经验,支持笔记本电脑是放在桌子下面的东西,无论是在办公室还是在那个星期被呼叫的人的家里。如果事情做得正确,笔记本电脑很少使用,更新更不频繁。当您考虑用于与 Kubernetes 交互的命令行工具的范围时,这是一个问题,其中一些工具(如helm)在版本控制方面可能特别多变。

通过从 runbook 执行kubectl和其他 Kubernetes CLI 工具,您不再需要安装本地工具。您所需要的只是一个 web 浏览器,以及配置了所需工具的 Octopus 服务器(或 Workers)。

不需要额外权限

如果您遵循最佳安全实践,那么您的 Kubernetes 集群将不会只有一个管理员帐户,而是为集群中的每个功能区域提供有限的服务帐户。这些帐户的密码将经常刷新,以限制任何泄露的凭据的潜在损害。

但是,您如何与您的支持团队分享这些凭证呢?

使用操作手册减轻了最终用户维护基础架构凭据的负担。每个 Kubernetes 目标可以根据需要对集群进行或多或少的访问,Octopus 权限可以限制谁可以在哪个目标上运行哪个 runbook。

用商业智能丰富 Kubernetes 脚本

kubectl是一个大锤子,如果您有所需的权限,它几乎可以在 Kubernetes 集群上做任何事情。但是它永远不会拥有特定于您的用例的商业智能。

在上面的示例脚本中,我们专门向 Kubernetes 查询了处于非运行状态的 pod。对 Kubernetes 来说,不运行的 pod 只是 pod 可能处于的一种状态,但在您的业务中,这可能是进行故障诊断时首先要寻找的。

这种商业知识往往是在漫长的支持之夜中来之不易的,然后通过个人之间的战争故事来分享。但是有了操作手册,它就可以变成一个标准流程,任何人都可以看到。

详细的 Kubernetes 审计跟踪

停机期间的首要任务是让集群重新联机。但是第二天就花在了尝试逆向工程问题的根本原因上。

  • 为什么那些特殊的豆荚被重启了?
  • 日志文件里有什么?
  • 在被删除之前,pod 使用了多少资源?

当运行kubectl的本地终端关闭时,这些问题的答案往往会丢失。

使用 runbooks,每个查询的结果都记录在日志文件中,每个操作的历史记录都记录在审计日志中。您还可以确保kubectl delete pod总是在kubectl logskubectl describe pod之前,使得被删除的 pod 的状态易于在第二天查看。

共同的背景

连续部署的最佳实践包括在整个环境中推动变化。高可用性意味着将您的生产基础架构跨可用性区域或地区分布,跨多个云提供商部署,或者拥有混合的内部和云基础架构。

Octopus 长期以来支持多个云提供商和内部部署,通过目标和环境捕获拓扑结构。Runbooks 利用相同的上下文,允许在现有基础架构上执行任务,而无需重新定义基础架构。

结论

在这样一个世界里,你需要学习 30 种不同的东西来部署一个应用程序,Octopus 努力为你提供一个简单的部署按钮。

借助 Runbooks,推动 Octopus 部署的经过实战检验的流程现在也可用于 DevOps 支持和维护任务。

愉快的部署!

DevOps 与 SDLC - Octopus 部署

原文:https://octopus.com/blog/devops-versus-sdlc

如果您使用传统的软件开发生命周期(SDLC ),您可能会对 DevOps 的适用范围有疑问。两者可以共存吗,还是冲突太多?

这篇文章阐述了这两种方法之间的区别。

什么是 SDLC?

生命周期的概念出现于 20 世纪 60 年代,它提供了一种构建和操作信息系统的结构化和系统化的方法。早期的系统开发项目包括软件和硬件,如林肯项目和 SAGE 项目,其中包括新的计算机内存技术的引入和软件的开发。

系统开发生命周期将阶段和控制步骤表达为一系列阶段,例如分析、设计和开发。整个生命周期涵盖了从最初构思到系统退役的所有内容。

软件开发生命周期最初将系统开发生命周期应用于软件项目。具体的阶段顺序是不同的,但是对于许多早期软件交付模型来说,总体概念是相同的。

最近,SDLC 被非正式地用来指代任何软件开发过程。由于我们不能将 DevOps 与每一个可能的过程进行比较(这是其中之一),我们将坚持 SDLC 的正式定义,将其作为软件交付的传统分阶段方法。

为什么要创建 SDLC?

你需要回到过去去理解 SDLC 的动机。自 1950 年以来,我们的软件交付的历史遵循开发过程的演变。我们发现技术在改变需求方面发挥了重要作用。就像科学家受到他们实验可用设备的限制一样,早期开发人员也受到运行成本高、编译时间长、代码编辑工具有限的稀缺机器的限制。

作为早期的程序员,你没有一个带有语法高亮、代码导航或编译器警告的集成开发环境。非编程角色的人也缺乏软件工具来帮助他们的工作或改善角色之间的交流。

关键是,你不能在网上寻找答案。你必须在手册的帮助下自己解决这个问题。

  • 1989 年的今天,蒂姆·伯纳斯·李发明了万维网
  • 1997 年的今天,谷歌发布了
  • 2008 -堆栈溢出到达

在引入 SDLC 之前,系统是使用专门的代码和修复方法创建的。由于没有定义过程或控制,并且有许多技术限制,分阶段模型解决了许多组织在创建大型应用程序时遇到的问题。

SDLC 解决了两类问题:

  1. 代码和修复方法扩展到大规模系统的问题
  2. 当时的具体技术限制

最初的分阶段模型是在麻省理工学院的林肯实验室创建的。他们的模型有 9 个阶段,旨在直接解决 20 世纪 50 年代软件团队面临的问题。这让他们能够扩展他们的开发工作,共享关于系统的信息,并记录哪里出错了,这样知识就可以与现有的和未来的贡献者共享。

林肯实验室使用的阶段是:

  1. 运营计划
  2. 机器和操作规范
  3. 程序规格
  4. 编码规范
  5. 编码
  6. 参数测试
  7. 组装测试
  8. 勒索
  9. 系统评价

SDLC 的许多变体是在不同的阶段创建的,随着业务和技术的发展而变化。

为什么 SDLC 成为一个问题

SDLC 中出现的问题有两个原因:

  1. 组织增加了其 SDLC 阶段的数量和复杂性
  2. SDLC 没有跟上改进工具的步伐

当您解决软件交付问题的主要工具是一组阶段和控制步骤时,您倾向于通过添加更多的阶段和控制步骤来解决大多数问题。随着您的过程规模的增长,它增加了每个软件版本的交易成本。当机器越来越便宜,编译速度越来越快时,重量级过程的成本却在增加。

最初引入 SDLC 是为了解决两个问题:

  1. 扩展软件开发以处理大规模系统
  2. 当时的具体技术限制

虽然第一个问题依然存在,但 1990 年的技术限制与 1960 年的完全不同。随着技术约束的消失,SDLC 本身成为软件交付的一个限制因素。在将过程视为目标,而不是实现组织成果的方法的组织中,SDLC 是一个更大的问题。

SDLC 告诉我们有就是,比如太多的进程

Process weight and effectiveness

能力与过程权重:与代码和修复相比,添加过程改进了软件交付,直到过程本身成为约束因素

批量大小、部署频率和风险之间存在复杂的关系。无论您如何严格地测试系统的功能和质量属性,市场风险仍然存在,直到您将软件版本发布给用户。你只知道一个功能在人们使用它的时候是有用的。

大批量还会导致自动化经济学中的一个常见错误。常识告诉我们,要么将你最常做的任务自动化;或者您计算任务的人工工作量乘以其频率。

这就产生了一个悖论,因为你不经常执行一项任务的原因是它是手动的而且昂贵。自动化确实减少了手工工作,但也让你更频繁地执行任务。任何自动化的经济模型都应该考虑:

  • 频率增加
  • 更高的质量
  • 更少的手动错误
  • 延迟成本的降低

除了技术限制的转变,一种新的竞争格局出现了,那些对市场反应迟钝的组织被更小更快的公司抢走了生意。

SDLC 在当时是正确的解决方案。事情发生了如此大的变化,它不再是软件交付的有效方法。

DevOps 有 SDLC 吗?

传统的 SDLC 不再被视为良好的实践。尽管通过适当的小心来避免它是可能的,但是对于传统的分阶段方法来说,更常见的是导致在专业团队之间传递的大批量。这与 DevOps 方法不兼容,devo PS 方法寻求减少批量的大小并增加不同学科之间的合作。

使用 SDLC,您可以将 20 个人分成 5 个专家团队,分别从事分析、设计、开发、测试和运营等阶段的工作。这些水平团队将执行他们的专业任务,工作从一个团队传递到另一个团队,就像接力赛中的接力棒。

在 DevOps 中,你可以将人们分成 4 个跨职能团队,他们可以交付软件而无需交接。你的垂直团队可以各自交付和运行一个独立的组件,就像橄榄球比赛中一排球员将球移向得分线。

如果你想了解更多关于团队设计的知识,马修·斯凯尔顿和曼纽尔·派斯创造了 团队拓扑 来描述不同的交互模式。你可以利用这些在你的组织中设计健康的沟通结构。

在 DevOps 和持续交付中,仍然需要完成一系列任务来交付软件。与结构化的 SDLC 不同,您专注于减少批量、创建自治的垂直团队,以及自动化您的部署管道。您可以设计一个补充 DevOps 文化和能力的过程。

DevOps 过程

持续交付是 DevOps 过程的核心,但是您仍然需要一种发现和共享构建内容的方法。您的上下文将指导您的选择,但下面显示了一个示例流程:

  1. 影响图:一种产生与目标相关的想法的协作技术。
  2. 通过实例的规格说明:一种使用实例来交流需求的方法,这可以转化为自动化的验收测试。
  3. 连续交付:围绕自动化部署管道构建的软件交付过程。

持续交付不依赖于任何产生想法的特定技术。例如,您可以从影响图切换到精益启动或您的产品管理工具包中的任何其他技术。DevOps 认识到您在持续交付的同时使用了适当的精益和敏捷技术。

SDLC 的遗产

对于一些组织来说,SDLC 的持久部分是一组重量级的阶段和控制,这些阶段和控制限制了软件交付并产生了高水平的市场风险。这是多年发现的一个不幸的负面结果。

SDLC 的真正遗产应该是我们从最初 40 年的软件交付中学到的东西:

  • 你应该小批量工作
  • 您应该经常部署
  • 从一个小的工作原型开始,然后发展它
  • 有可能有太多的过程

有了正确的文化和能力,以及尽可能自动化的部署管道,您应该能够交付频繁的高质量软件版本。

结论

当您现在考虑软件开发生命周期时,只需采用您的现代 DevOps 过程并将其扩展到包含原始想法的创建,并考虑当软件不再有足够的价值来维护时,如何使其退役。最重要的部分(你不断增加软件价值和改进其操作的中间部分)是你花费大部分时间和金钱的地方。

考虑到潜在约束和竞争的变化,基于系统开发生命周期的传统形式化方法不再适合软件交付。

了解更多信息

如果您想了解更多关于 DevOps 和持续交付的信息,您可能会发现以下内容非常有用:

愉快的部署!

实施 DevSecOps 以应对漏洞- Octopus Deploy

原文:https://octopus.com/blog/devsecops-respond-to-vulnerabilities

Log4j 项目在 2021 年末因一个关键漏洞成为关注的焦点。它引发了来自全球公司和框架开发者的大量“我们对 Log4j 的回应”公告,甚至更多的客户请求支持服务台和论坛来评估他们的暴露程度。

许多工程团队发现自己处于压力之下,需要识别任何受影响的代码库,提供描述任何暴露的准确报告,更新任何所需的依赖项,并尽快将新代码部署到生产环境中。

任何面临这一挑战的人都知道,这样的回应并不像听起来那么容易。像任何代码依赖一样,知道您的代码是否使用 Log4j 需要对您的应用程序的结构和当前部署的版本有深刻的理解。通常,这需要在表示应用程序部署版本的特定 git 提交处检查您的代码库,并深入研究您的直接依赖项及其子依赖项,以准确找到您的代码使用的库。

Runbooks 与构建信息和对 CI/CD 管道的一些简单更改相结合,为查询部署的应用程序中包含的依赖项提供了一种方便的方法。

在这篇文章中,您将学习如何修改 GitHub Actions 工作流来捕获特定构建所使用的依赖项,并查看可以按需查询信息的示例操作手册。

先决条件

这篇文章使用 GitHub Actions 作为 CI 服务器。GitHub Actions 对公共 git 库是免费的,所以你只需要一个 GitHub 账户就可以开始了。

示例 runbook 脚本是针对 Python 3 编写的,可以从 Python 网站下载。你可以在 GitHub 上找到示例 runbook 源代码。

在构建过程中捕获依赖关系

首先,作为 GitHub Actions 工作流的一部分,捕获构建过程所消耗的依赖项。如今,每种主要语言都提供了列出依赖项的能力,下面的列表显示了这些命令的示例,将输出捕获到一个名为dependencies.txt的文件中:

  • 腹部—mvn --batch-mode dependency:tree --no-transfer-progress > dependencies.txt
  • 度—gradle dependencies --console=plain > dependencies.txt
  • Npm - npm list --all > dependencies.txt
  • PHP - composer show --all > dependencies.txt
  • Python - pip install pipdeptree; pipdeptree > dependencies.txt
  • 去- go list > dependencies.txt
  • 红宝石- gem dep > dependencies.txt
  • 点网核心- dotnet list package > dependencies.txt

必须向 GitHub Actions 工作流添加两个步骤,以将依赖项捕获为工件。下面的例子演示了如何捕获 Maven 依赖项,但是对于您的特定用例,List Dependencies步骤的run属性可以替换为上面的任何命令:

 - name: List Dependencies
      run: mvn --batch-mode dependency:tree --no-transfer-progress > dependencies.txt
      shell: bash
    - name: Collect Dependencies
      uses: actions/upload-artifact@v2
      with:
        name: Dependencies
        path: dependencies.txt 

下面的屏幕截图显示了与构建相关的工件:

Dependencies Artifact

生成构建信息

构建信息为 Octopus 部署或 runbook 中引用的包提供额外的元数据。构建信息包是存储在 Octopus 服务器上的单独的工件,具有与它们所代表的包相同的包 ID 和版本。这使得 Octopus 可以跟踪各种包的元数据,无论是存储在内置提要中还是托管在外部存储库中。

构建信息包捕获的一个属性是返回到生成该包的 CI 构建的链接。下面的截图显示了 GitHub 操作运行的链接:

Build link

XO-energy/action-octopus-build-information动作提供了创建和上传构建信息包的能力。以下步骤显示了一个操作示例:

 - name: Generate Octopus Deploy build information
      uses: xo-energy/action-octopus-build-information@v1.1.2
      with:
        octopus_api_key: ${{ inputs.octopus_api_token }}
        octopus_project: Products Service
        octopus_server: ${{ inputs.octopus_server_url }}
        push_version: 0.1.${{ inputs.run_number }}${{ env.BRANCH_NAME != 'master' && format('-{0}', env.BRANCH_NAME) || ''  }}
        push_package_ids: com.octopus.octopub:products-service
        push_overwrite_mode: OverwriteExisting
        output_path: octopus
        octopus_space: "Octopub"
        octopus_environment: "Development" 

Octopus 只需要推送构建信息包就可以将元数据链接到发行版。只要构建信息包 ID 和版本与 Octopus 步骤中使用的包相匹配,构建信息就会链接到发布。

下一步是编写一个定制脚本来查询 Octopus API,以提取给定环境中 CI 服务器最新版本的链接。

查询构建信息以下载 CI 工件

现在您已经有了跟踪 Octopus 版本中使用的任何包的依赖关系的所有信息。可以手动遍历 Octopus UI 中显示的链接,返回到 GitHub 操作运行,下载依赖项工件,并扫描其中的文本文件。但是这种手动工作流不能随着应用程序数量的增加而扩展。相反,您希望通过执行自定义 Python 脚本的 runbook 来自动化该过程。

第一步是在文件requirements.txt中定义脚本的依赖关系。该脚本利用请求包来简化 HTTP 请求:

requests==2.27.1 

然后创建一个名为main.py的文件来保存脚本。完整的代码如下所示:

import os
import sys
from datetime import datetime
from functools import cmp_to_key
from requests.auth import HTTPBasicAuth
import tempfile
from requests import get
import zipfile
import argparse

parser = argparse.ArgumentParser(description='Scan a deployment for a dependency.')
parser.add_argument('--octopusUrl', 
                    dest='octopus_url', 
                    action='store', 
                    help='The Octopus server URL',
                    required=True)
parser.add_argument('--octopusApiKey', 
                    dest='octopus_api_key', 
                    action='store', 
                    help='The Octopus API key',
                    required=True)
parser.add_argument('--githubUser', 
                    dest='github_user', 
                    action='store', 
                    help='The GitHub username',
                    required=True)
parser.add_argument('--githubToken', 
                    dest='github_token', 
                    action='store', 
                    help='The GitHub token/password',
                    required=True)
parser.add_argument('--octopusSpace', 
                    dest='octopus_space', 
                    action='store', 
                    help='The Octopus space',
                    required=True)
parser.add_argument('--octopusProject', 
                    dest='octopus_project', 
                    action='store',
                    help='A comma separated list of Octopus projects', 
                    required=True)
parser.add_argument('--octopusEnvironment', 
                    dest='octopus_environment', 
                    action='store', 
                    help='The Octopus environment',
                    required=True)
parser.add_argument('--searchText', 
                    dest='search_text', 
                    action='store',
                    help='The text to search for in the list of dependencies',
                    required=True)
parser.add_argument('--githubDependencyArtifactName', 
                    default="Dependencies", 
                    dest='github_dependency_artifact',
                    action='store',
                    help='The name of the GitHub Action run artifact that contains the dependencies')

args = parser.parse_args()

headers = {"X-Octopus-ApiKey": args.octopus_api_key}
github_auth = HTTPBasicAuth(args.github_user, args.github_token)

def compare_dates(date1, date2):
    # Python 3.6 doesn't handle the colon in the timezone of a string like "2022-01-04T04:23:02.941+00:00".
    # So we need to manually strip it out.
    date1_parsed = datetime.strptime(date1["Created"][:-3] + date1["Created"][-2:], '%Y-%m-%dT%H:%M:%S.%f%z')
    date2_parsed = datetime.strptime(date2["Created"][:-3] + date2["Created"][-2:], '%Y-%m-%dT%H:%M:%S.%f%z')
    if date1_parsed < date2_parsed:
        return -1
    if date1_parsed == date2_parsed:
        return 0
    return 1

def get_space_id(space_name):
    url = args.octopus_url + "/api/spaces?partialName=" + space_name.strip() + "&take=1000"
    response = get(url, headers=headers)
    spaces_json = response.json()

    filtered_items = [a for a in spaces_json["Items"] if a["Name"] == space_name.strip()]

    if len(filtered_items) == 0:
        sys.stderr.write("The space called " + space_name + " could not be found.\n")
        return None

    first_id = filtered_items[0]["Id"]
    return first_id

def get_resource_id(space_id, resource_type, resource_name):
    if space_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/" + resource_type + "?partialName=" \
        + resource_name.strip() + "&take=1000"
    response = get(url, headers=headers)
    json = response.json()

    filtered_items = [a for a in json["Items"] if a["Name"] == resource_name.strip()]
    if len(filtered_items) == 0:
        sys.stderr.write("The resource called " + resource_name + " could not be found in space " + space_id + ".\n")
        return None

    first_id = filtered_items[0]["Id"]
    return first_id

def get_release_id(space_id, environment_id, project_id):
    if space_id is None or environment_id is None or project_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/deployments?environments=" + environment_id + "&take=1000"
    response = get(url, headers=headers)
    json = response.json()

    filtered_items = [a for a in json["Items"] if a["ProjectId"] == project_id]
    if len(filtered_items) == 0:
        sys.stderr.write("The project id " + project_id + " did not have a deployment in " + space_id + ".\n")
        return None

    sorted_list = sorted(filtered_items, key=cmp_to_key(compare_dates), reverse=True)
    release_id = sorted_list[0]["ReleaseId"]

    return release_id

def get_build_urls(space_id, release_id, project):
    if space_id is None or release_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/releases/" + release_id
    response = get(url, headers=headers)
    json = response.json()

    build_information_with_urls = [a for a in json["BuildInformation"] if "github.com" in a["BuildUrl"]]
    build_urls = list(map(lambda b: b["BuildUrl"], build_information_with_urls))

    if len(build_urls) == 0:
        sys.stderr.write("No build information results contained build URLs to GitHub for project "
                         + project.strip() + ".\n")
        sys.stderr.write("This script assumes GitHub Actions were used to build the packages deployed by Octopus.\n")

    return build_urls

def download_file(url):
    with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp_file:
        # get request
        response = get(url, auth=github_auth)
        # write to file
        tmp_file.write(response.content)
        return tmp_file.name

def get_artifacts(build_urls, dependency_artifact_name):
    if build_urls is None:
        return None

    files = []

    for url in build_urls:
        # turn https://github.com/OctopusSamples/OctoPub/actions/runs/1660462851 into
        # https://api.github.com/repos/OctopusSamples/OctoPub/actions/runs/1660462851/artifacts
        artifacts_api_url = url.replace("github.com", "api.github.com/repos") + "/artifacts"
        response = get(artifacts_api_url, auth=github_auth)
        artifact_json = response.json()

        filtered_items = [a for a in artifact_json["artifacts"] if a["name"] == dependency_artifact_name]

        if len(filtered_items) == 0:
            print("No artifacts were found in the GitHub Action run called " + dependency_artifact_name)

        for artifact in filtered_items:
            artifact_url = artifact["archive_download_url"]
            files.append(download_file(artifact_url))

    return files

def unzip_files(zip_files):
    if zip_files is None:
        return None

    text_files = []
    for file in zip_files:
        with zipfile.ZipFile(file, 'r') as zip_ref:
            with tempfile.TemporaryDirectory() as tmp_dir:
                zip_ref.extractall(tmp_dir)
                for extracted_file in os.listdir(tmp_dir):
                    filename = os.fsdecode(extracted_file)
                    if filename.endswith(".txt"):
                        with open(os.path.join(tmp_dir, extracted_file)) as f:
                            content = f.read()
                            text_files.append(content)
    return text_files

def search_files(text_files, text, project):
    found = False
    for file in text_files:
        if text in file:
            found = True
            print(text + " found in the following list of dependencies for project " + project.strip())
            print(file)

    return found

def scan_dependencies():
    space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment)
    found = False
    for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project)
        release_id = get_release_id(space_id, environment_id, project_id)
        urls = get_build_urls(space_id, release_id, project)
        files = get_artifacts(urls, args.github_dependency_artifact)
        text_files = unzip_files(files)
        if search_files(text_files, args.search_text, project):
            found = True

    print("Searching project(s) " + args.octopus_project + " for dependency " + args.search_text)
    if found:
        print("\n\nSearch text " + args.search_text + " was found in the list of dependencies.")
        print("See the logs above for the complete text file listing the application dependencies.")
    else:
        print("\n\nSearch text " + args.search_text + " was not found in any dependencies.")

scan_dependencies() 

让我们分解这段代码,了解它在做什么。

您的脚本接受来自命令行参数的参数,使其可以跨多个 Octopus 实例和空间重用。参数由 argparse 模块解析。在 Real Python 的帖子中了解更多关于使用argparse的信息,如何用 argparse 在 Python 中构建命令行接口:

parser = argparse.ArgumentParser(description='Scan a deployment for a dependency.')
parser.add_argument('--octopusUrl', 
                    dest='octopus_url', 
                    action='store', 
                    help='The Octopus server URL',
                    required=True)
parser.add_argument('--octopusApiKey', 
                    dest='octopus_api_key', 
                    action='store', 
                    help='The Octopus API key',
                    required=True)
parser.add_argument('--githubUser', 
                    dest='github_user', 
                    action='store', 
                    help='The GitHub username',
                    required=True)
parser.add_argument('--githubToken', 
                    dest='github_token', 
                    action='store', 
                    help='The GitHub token/password',
                    required=True)
parser.add_argument('--octopusSpace', 
                    dest='octopus_space', 
                    action='store', 
                    help='The Octopus space',
                    required=True)
parser.add_argument('--octopusProject', 
                    dest='octopus_project', 
                    action='store',
                    help='A comma separated list of Octopus projects', 
                    required=True)
parser.add_argument('--octopusEnvironment', 
                    dest='octopus_environment', 
                    action='store', 
                    help='The Octopus environment',
                    required=True)
parser.add_argument('--searchText', 
                    dest='search_text', 
                    action='store',
                    help='The text to search for in the list of dependencies',
                    required=True)
parser.add_argument('--githubDependencyArtifactName', 
                    default="Dependencies", 
                    dest='github_dependency_artifact',
                    action='store',
                    help='The name of the GitHub Action run artifact that contains the dependencies')

args = parser.parse_args() 

该脚本向 Octopus 和 GitHub APIs 发出许多请求,所有请求都需要认证。

Octopus API 使用X-Octopus-ApiKey头来传递用于认证请求的 API 密钥。你可以在 Octopus 文档中找到更多关于如何创建 API 的信息。

GitHub API 使用标准的 HTTP 基本认证,密码使用个人访问令牌。GitHub 文档提供了创建令牌的细节。

下面的代码捕获包含凭证的对象,这些凭证通过脚本的其余部分随每个 API 请求传递:

headers = {"X-Octopus-ApiKey": args.octopus_api_key}
github_auth = HTTPBasicAuth(args.github_user, args.github_token) 

这个脚本的一个重要方面是能够找到部署到给定环境的最新版本。这意味着比较由 Octopus API 返回的日期。

Octopus API 返回 ISO 8601 格式的日期,看起来像2022-01-04T04:23:02.941+00:00。不幸的是, Python 3.6 不支持包含冒号的时区偏移量,这迫使您在解析和比较日期之前将它们去掉。

compare_dates函数将两个日期作为字符串,去掉冒号,解析结果,并返回一个值10-1,表示date1date2相比如何:

def compare_dates(date1, date2):
    # Python 3.6 doesn't handle the colon in the timezone of a string like "2022-01-04T04:23:02.941+00:00".
    # So we need to manually strip it out.
    date1_parsed = datetime.strptime(date1["Created"][:-3] + date1["Created"][-2:], '%Y-%m-%dT%H:%M:%S.%f%z')
    date2_parsed = datetime.strptime(date2["Created"][:-3] + date2["Created"][-2:], '%Y-%m-%dT%H:%M:%S.%f%z')
    if date1_parsed < date2_parsed:
        return -1
    if date1_parsed == date2_parsed:
        return 0
    return 1 

这个脚本(以及大多数使用 Octopus API 的脚本)中的一个常见模式是查找命名资源的 ID。get_space_id函数获取 Octopus 空间的名称,并查询 API 以返回空间 ID:

def get_space_id(space_name): 

/api/spaces端点返回 Octopus 服务器中定义的空间列表。partialName查询参数将结果限制为名称包含所提供值的空格,而take参数被设置为一个较大的数字,以确保您不需要循环任何分页的结果:

 url = args.octopus_url + "/api/spaces?partialName=" + space_name.strip() + "&take=1000" 

对端点发出 GET HTTP 请求,包括 Octopus 身份验证头,JSON 结果被解析到 Python 嵌套字典中:

 response = get(url, headers=headers)
    spaces_json = response.json() 

返回的结果可以匹配名称为或包含所提供的空间名称的任何空间。这意味着如果我们搜索名为MySpace的空间,将返回名为MySpaceMySpaceTwo的空间。

为了确保用正确的名称返回空间的 ID,一个列表理解将返回的空间过滤为与提供的空间名称完全匹配的空间:

 filtered_items = [a for a in spaces_json["Items"] if a["Name"] == space_name.strip()] 

如果没有空格与提供的空格名称匹配,该函数将返回None:

 if len(filtered_items) == 0:
        sys.stderr.write("The space called " + space_name + " could not be found.\n")
        return None 

如果有匹配的空格,则返回 ID:

 first_id = filtered_items[0]["Id"]
    return first_id 

空间是 Octopus 中的顶级资源,而您在该脚本中与之交互的所有其他资源都是空间的子资源。正如您对get_space_id函数所做的一样,get_resource_id函数将一个已命名的 Octopus 资源转换成它的 id。这里唯一的区别是所请求的端点在路径中包含了空间 ID,并且提供了资源类型来构建路径中的第二个元素。否则get_resource_id遵循为get_space_id功能描述的相同模式:

def get_resource_id(space_id, resource_type, resource_name):
    if space_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/" + resource_type + "?partialName=" \
        + resource_name.strip() + "&take=1000"
    response = get(url, headers=headers)
    json = response.json()

    filtered_items = [a for a in json["Items"] if a["Name"] == resource_name.strip()]
    if len(filtered_items) == 0:
        sys.stderr.write("The resource called " + resource_name + " could not be found in space " + space_id + ".\n")
        return None

    first_id = filtered_items[0]["Id"]
    return first_id 

现在,您需要提供一种方法来确定最后一次部署到所选项目的所选环境中的版本。

发布是部署过程、包版本和变量的快照。这是当您单击 Octopus UI 中的 CREATE RELEASE 按钮时创建的资源。

然后,部署是对环境发布的执行。

因此,为了找到哪个包版本被部署到指定的环境中(在处理漏洞时,我们通常指的是生产环境),您必须列出一个环境的部署,将它们过滤到指定项目的部署中,对部署进行排序以确保您找到最新的版本,并返回部署执行的版本的 ID。

get_release_id函数实现了这个查询:

def get_release_id(space_id, environment_id, project_id):
    if space_id is None or environment_id is None or project_id is None:
        return None 

您查询/deployments端点以返回部署列表,并传递environments查询参数以将结果限制为指定环境的那些部署:

 url = args.octopus_url + "/api/" + space_id + "/deployments?environments=" + environment_id + "&take=1000"
    response = get(url, headers=headers)
    json = response.json() 

结果列表被过滤为与指定项目相关的项目:

 filtered_items = [a for a in json["Items"] if a["ProjectId"] == project_id]
    if len(filtered_items) == 0:
        sys.stderr.write("The project id " + project_id + " did not have a deployment in " + space_id + ".\n")
        return None 

对过滤后的列表进行排序,以确保最新部署是第一项:

 sorted_list = sorted(filtered_items, key=cmp_to_key(compare_dates), reverse=True) 

然后返回最新部署的发布 ID:

 release_id = sorted_list[0]["ReleaseId"]

    return release_id 

正如您在前面看到的,当构建信息可用于发布中引用的包时,详细信息(如返回到创建该包的 CI 构建的链接)也是可用的。这个链接暴露在 Octopus API 返回的 release 资源中,由get_build_urls函数提取:

def get_build_urls(space_id, release_id, project):
    if space_id is None or release_id is None:
        return None 

您查询 Octopus API 以从指定的发布 ID 返回完整的发布资源:

 url = args.octopus_url + "/api/" + space_id + "/releases/" + release_id
    response = get(url, headers=headers)
    json = response.json() 

每个版本可以有多个相关的构建信息资源,每个资源都被过滤以找到任何包含github.com的构建 URL,这表明该包是使用 GitHub Actions 构建的:

 build_information_with_urls = [a for a in json["BuildInformation"] if "github.com" in a["BuildUrl"]] 

包含所有构建信息的字典被展平为一个链接数组:

 build_urls = list(map(lambda b: b["BuildUrl"], build_information_with_urls)) 

如果没有找到返回 GitHub 的链接,则会显示一条警告消息:

 if len(build_urls) == 0:
        sys.stderr.write("No build information results contained build URLs to GitHub for project "
                         + project.strip() + ".\n")
        sys.stderr.write("This script assumes GitHub Actions were used to build the packages deployed by Octopus.\n") 

然后返回 URL 列表:

 return build_urls 

GitHub 动作工件是简单的 zip 文件,可以像普通文件一样从任何其他 web 服务器上下载。download_file函数创建一个临时文件,并将提供的 URL 的内容写入其中,返回文件名:

def download_file(url):
    with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp_file:
        # get request
        response = get(url, auth=github_auth)
        # write to file
        tmp_file.write(response.content)
        return tmp_file.name 

get_artifacts函数向 GitHub API 查询与运行相关的工件的链接:

def get_artifacts(build_urls, dependency_artifact_name):
    if build_urls is None:
        return None

    files = [] 

由构建信息捕获的 GitHub 链接指向公共的、可浏览的网页,显示 GitHub 操作运行的结果。为了以编程方式与运行细节进行交互,您需要与 GitHub API 进行交互。

GitHub 有两个相关的 URL 结构。以https://github.com/开头的 URL 公开网页浏览器的 HTML 页面,而以https://api.github.com/repos/开头的 URL 公开 GitHub API。除了前缀之外,这两个 URL 结构基本相同。

这意味着我们必须将类似于https://github.com/OctopusSamples/OctoPub/actions/runs/1660462851的 URL 转换成https://api.github.com/repos/OctopusSamples/OctoPub/actions/runs/1660462851才能访问 GitHub API:

 for url in build_urls:
        # turn https://github.com/OctopusSamples/OctoPub/actions/runs/1660462851 into
        # https://api.github.com/repos/OctopusSamples/OctoPub/actions/runs/1660462851/artifacts
        artifacts_api_url = url.replace("github.com", "api.github.com/repos") + "/artifacts"
        response = get(artifacts_api_url, auth=github_auth)
        artifact_json = response.json() 

对工件列表进行过滤,以返回与预期工件名称相匹配的工件:

 filtered_items = [a for a in artifact_json["artifacts"] if a["name"] == dependency_artifact_name] 

如果没有匹配的工件,将显示一个错误:

 if len(filtered_items) == 0:
            print("No artifacts were found in the GitHub Action run called " + dependency_artifact_name) 

对于每个匹配的工件,提取下载 URL,并返回 URL 列表:

 for artifact in filtered_items:
            artifact_url = artifact["archive_download_url"]
            files.append(download_file(artifact_url))

    return files 

GitHub 工件应该是包含文本文件的 zip 文件。unzip_files函数提取提供的文件,扫描结果文件中的文本文件,读取文本文件,并返回文本:

def unzip_files(zip_files):
    if zip_files is None:
        return None

    text_files = []
    for file in zip_files:
        with zipfile.ZipFile(file, 'r') as zip_ref:
            with tempfile.TemporaryDirectory() as tmp_dir:
                zip_ref.extractall(tmp_dir)
                for extracted_file in os.listdir(tmp_dir):
                    filename = os.fsdecode(extracted_file)
                    if filename.endswith(".txt"):
                        with open(os.path.join(tmp_dir, extracted_file)) as f:
                            content = f.read()
                            text_files.append(content)
    return 

search_files函数从提取的文本文件的内容中打印任何包含指定搜索字符串的依赖列表:

def search_files(text_files, text, project):
    found = False
    for file in text_files:
        if text in file:
            found = True
            print(text + " found in the following list of dependencies for project " + project.strip())
            print(file)

    return found 

scan_dependencies函数中,上面的所有代码被组合在一起,以扫描所提供文本的依赖关系列表:

def scan_dependencies(): 

空间名称被转换成一个 ID:

 space_id = get_space_id(args.octopus_space) 

环境名被转换成一个 ID:

 environment_id = get_resource_id(space_id, "environments", args.octopus_environment) 

这些项目可以以逗号分隔的列表形式提供,您可以对其进行循环:

 found = False
    for project in args.octopus_project.split(","): 

项目名称被转换成一个 ID:

 project_id = get_resource_id(space_id, "projects", project) 

找到与项目到环境的最新部署相关联的版本:

 release_id = get_release_id(space_id, environment_id, project_id) 

找到了工件下载 URL:

 urls = get_build_urls(space_id, release_id, project) 

然后下载每个 URL:

 files = get_artifacts(urls, args.github_dependency_artifact) 

解压缩 zip 文件,并读取其中包含的任何文本文件:

 text_files = unzip_files(files) 

扫描这些文本文件的内容以查找搜索字符串:

 if search_files(text_files, args.search_text, project):
            found = True 

然后打印汇总信息:

 print("Searching project(s) " + args.octopus_project + " for dependency " + args.search_text)
    if found:
        print("\n\nSearch text " + args.search_text + " was found in the list of dependencies.")
        print("See the logs above for the complete text file listing the application dependencies.")
    else:
        print("\n\nSearch text " + args.search_text + " was not found in any dependencies.") 

最后一步是调用scan_dependencies函数,这将启动脚本:

scan_dependencies() 

脚本写好了,是时候在 runbook 中执行它了。

在操作手册中运行脚本

第一步是公开将传递给脚本的 3 个变量:

  • GitHubToken是一个保存 GitHub 个人访问令牌的秘密,用于认证 GitHub API 调用。
  • ReadOnlyApiKey是一个 Octopus API 密钥,分配给一个对 Octopus 服务器具有只读访问权限的帐户(因为这个脚本只查询 API,从不修改任何资源)。
  • SearchText是一个提示变量,定义在依赖文本文件中搜索的文本。

Octopus variables

runbook 由单个运行脚本步骤和以下 Bash 脚本组成:

cd DependencyQuery

echo "##octopus[stdout-verbose]"
python3 -m venv my_env
. my_env/bin/activate
pip --disable-pip-version-check install -r requirements.txt
echo "##octopus[stdout-default]"

python3 main.py \
    --octopusUrl https://tenpillars.octopus.app \
    --octopusApiKey "#{ReadOnlyApiKey}" \
    --githubUser mcasperson \
    --githubToken "#{GitHubToken}" \
    --octopusSpace "#{Octopus.Space.Name}" \
    --octopusEnvironment "#{Octopus.Environment.Name}" \
    --octopusProject "Products Service, Audits Service, Octopub Frontend" \
    --searchText "#{SearchText}" 

这个脚本中发生了一些有趣的事情,所以让我们来分析一下。

您输入 Octopus 解压包含 Python 脚本的包的目录:

cd DependencyQuery 

打印服务消息 ##octopus[stdout-verbose]指示 Octopus 将所有后续日志消息视为冗余:

echo "##octopus[stdout-verbose]" 

创建并激活一个名为my_env的新 Python 虚拟环境,并安装脚本依赖项:

python3 -m venv my_env
. my_env/bin/activate
pip --disable-pip-version-check install -r requirements.txt 

服务消息##octopus[stdout-default]被打印,指示 Octopus 再次以默认级别处理后续日志消息:

echo "##octopus[stdout-default]" 

然后执行 Python 脚本。有些参数,如octopusUrlgithubUseroctopusProject,需要根据您的具体用例进行定制。将octopusSpaceoctopusEnvironment参数设置为运行 runbook 的空间和环境允许您在运行 runbook 的任何环境中查找依赖关系:

python3 main.py \
    --octopusUrl https://tenpillars.octopus.app \
    --octopusApiKey "#{ReadOnlyApiKey}" \
    --githubUser mcasperson \
    --githubToken "#{GitHubToken}" \
    --octopusSpace "#{Octopus.Space.Name}" \
    --octopusEnvironment "#{Octopus.Environment.Name}" \
    --octopusProject "Products Service, Audits Service, Octopub Frontend" \
    --searchText "#{SearchText}" 

执行操作手册

当 runbook 被执行时,它会扫描每个项目以获取当前环境的最新部署,从构建信息中找到 GitHub Action run 链接,下载依赖项工件,提取工件,并扫描文本文件以获取搜索文本。

只需单击一下 RUN 按钮,您就可以快速搜索 Octopus 部署的任何项目,这些项目都有相关的构建信息和所需的构建工件:

Runbook run

结论

Log4j 让许多工程团队认识到依赖漏洞是生活中的一个事实,及时的响应不仅对限制您暴露于漏洞是至关重要的,而且对减少支持团队回答客户问题的压力也是至关重要的。同样清楚的是,Log4j 不会是最后一个广泛传播的漏洞,您的代码库受到泄漏的影响只是时间问题。

在这篇文章中,您了解了如何将应用程序构建所消耗的依赖项列表保存为 GitHub 操作中的工件,如何使用构建信息将运行链接到它们生成的包,然后使用运行手册执行自定义 Python 脚本,对部署到环境中的包中包含的依赖项执行简单的文本匹配。最终结果是能够在几分钟内知道您的应用程序是否暴露于依赖关系中报告的漏洞,并几乎立即开始响应。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

持续集成与持续部署——Octopus 部署

原文:https://octopus.com/blog/difference-between-ci-and-cd

术语持续集成持续交付/部署倾向于组合成首字母缩写词 CI/CD 来描述构建和部署软件的过程,通常两者之间没有区别。这些术语描述了不同的过程,即使将它们组合起来意味着持续交付和持续部署是持续集成的扩展,并且两个过程的执行是单个工具的责任。

假设 CI/CD 是只是 CI 和部署步骤忽略了两个过程之间的一些基本差异。在本帖中,我们来看看:

  • CI 和 CD 是不同过程的原因
  • 好的 CD 工具所提供的特性
  • 为什么您会考虑为您的 CI/CD 工作流程使用单独的工具

什么是持续集成?

在高层次上,持续集成工具涉及到:

  • 将开发人员编写的代码编译成工件
  • 运行自动化测试
  • 捕获日志文件,以便可以解决任何失败的构建或测试

持续集成服务器通过在每次提交时运行构建和测试来促进这一过程。

持续集成服务器可以描述为求解以下等式:

code + dependencies + build tools + execution environment = test results + logs + compiled artifact

Continuous Integration inputs and output graphic

等式的左边是开发人员编写的代码、代码的任何依赖项、构建工具以及执行构建和测试的环境。当这些输入可用时,持续集成服务器完成构建以产生等式右侧的元素。

当持续集成服务器被正确配置后,每次提交到存储库都会导致构建运行,从而无需人工干预即可求解方程。

这意味着持续集成服务器实现的过程是机器驱动的,以至于持续集成服务器通常具有只读用户界面,如 Jenkins Blue Ocean UI。

持续集成等式的另一个重要方面是开发人员提供输入,而输出是为开发人员或其他技术角色创建的。IT 部门以外的员工很少与持续集成服务器进行交互。

什么是持续部署和持续交付?

持续部署从持续集成服务器执行的成功构建中提取已编译的工件,并将它们部署到生产环境中,从而形成完全自动化的部署工作流。在这个场景中,持续部署是持续集成的扩展,两者之间的区别变得有些随意。

这种提交给消费者的工作流在简单的项目中很常见。如果有适当的测试和监控系统,更复杂的项目也可以有完全自动化的部署工作流。

虽然全自动部署有很多好处,但涉及人工决策的部署并不少见。有许多正当的理由不将对主分支的每个提交自动部署到生产中,包括:

  • 协调与遗留系统的部署
  • 获得产品负责人的签署
  • 不可能自动化的可用性测试
  • 监管要求
  • 狗食你自己的产品
  • 将部署与后端更改(如数据库)相集成
  • 对你的测试没有 100%的信心

术语连续交付用于区分包含人工决策的工作流和完全自动化的连续部署工作流。

对于许多团队来说,持续集成工具是机器驱动的,而持续交付是人驱动的。执行部署的大部分繁重工作仍然是自动化的,但是将发布升级到生产环境是人的决定。重要的是,决策可能不是由技术人员做出的,而是由产品负责人、经理或熬夜点击部署按钮的人做出的。

ci-cd-pipeline-diagram

典型的 CI/CD 管道,两者没有区别。

这张幻灯片来自题为如何在 Kubernetes 上使用 Tekton 构建云原生 CI/CD 管道的演讲。这是一个简单项目如何将持续集成和持续部署合并到一个过程中的经典例子,在这个过程中,代码一经编译,生产部署就开始了。

这一过程没有任何问题,如果管道的每个部分都保持完全自动化,它就会按预期工作。但是,如果人们需要在应用程序发布之前对其进行测试和批准,会发生什么呢?

为了做出这个决定,必须中断部署过程。例如,我们首先将应用程序部署到一个测试环境中,允许适当的人员验证变更,当每个人都满意时,发布就被提升到生产环境中。

这个单一的决策点意味着我们曾经由机器驱动的等式现在变成了:

  • 需要一个 UI 来公开已经发布到测试环境中的版本
  • 引入了审计和安全问题,因此我们可以限制并审查谁向哪些环境发布了哪些版本
  • 需要一个 UI 来允许部署提升到下一个环境
  • 需要一个能够以一流方式模拟环境的系统,以便能够通过 UI、API 和报告界面可靠地保护和管理环境

【T2 Octopus dashboard showing Projects overview page with releases in Dev, Test and Production sometimes approved sometimes failing

带人类展开按钮的章鱼仪表盘。

当 CI/CD 仅仅作为一个部署步骤,在代码编译后自动执行时,这种对人的因素的关注经常会丢失。例如, Jenkins 文档建议将测试和生产环境建模为持续集成管道中的阶段。

乍一看,这个例子似乎提供了流程中的一个点来批准部署,但是对于一个从来没有打算推向生产的构建会发生什么呢?这样的构建会在应用程序向客户公开之前被取消,导致构建失败。这些失败的构建很难与编译失败或测试失败的构建区分开来,即使在这种情况下,不提升到生产是连续交付过程的预期行为。

简而言之,一个好的部署工具,如 Octopus Deploy,可以促进部署中常见的(如果不是必需的)人工决策过程,或者至少可以显示环境之间部署的当前状态,并自动执行部署,因此环境之间的提升是容易和可靠的。

结论

认识到机器驱动的持续集成过程和人工驱动的持续交付过程之间的不同需求,对于以快速、可靠和可重复的方式向客户交付特性是至关重要的。这就是为什么使用专用工具进行持续集成和持续交付是有意义的。你可以试试用 GitHub构建,用 Octopus 部署。

如果你以前没用过八达通,你可以注册免费试用

探索 DevOps 工程师手册以了解关于 DevOps 和 CI/CD 的更多信息,或者阅读我们的持续集成系列的其余部分。

愉快的部署!

docker.io、docker-cd 和 Docker Desktop - Octopus Deploy 之间的区别

原文:https://octopus.com/blog/difference-between-docker-versions

经过多年的发展,Docker 已经成熟,可以为使用容器的开发人员提供一系列解决方案。这可能会导致一些混乱,因为开发人员需要选择安装哪个版本的 Docker。

在这篇文章中,我将介绍哪些操作系统可以使用哪些选项,并提供如何选择的建议。

Docker 的多面性

Docker 已经成为一个大的、正在进行的运动的一部分,该运动集中于构建、分发和运行容器化软件。虽然术语“Docker”通常是容器化的同义词,但是理解各种工具和规范协同工作来支持容器化软件是值得的。

支持 Docker 的核心技术由开放容器倡议 (OCI)定义,它用图像规范(image-spec)定义了可分发图像的格式,以及这些图像如何用运行时规范(runtime-spec)运行。

OCI 运行时规范由多个容器运行时实现,包括 runccrunkatacontainers 。容器运行时执行执行容器化流程所需的低级工作。

容器引擎如带有容器的 Docker和带有 conmonPodman 用于通过容器运行时拉取 OCI 映像和启动容器。

开发者和用于构建、分发和运行容器化软件的软件栈之间的接口在技术上是容器引擎,在 Docker 的例子中,容器引擎是docker命令行工具。

容器运行时和 Docker 容器引擎的组合被 Docker 网站称为 Docker 引擎

现在我们更清楚我们所说的“Docker”是什么意思了,是时候看看开发者在使用容器化应用时可用的选项了。

Linux 用户的选择太多了

容器化技术最初是在 Linux 中开发的,所以 Linux 用户在使用容器化应用程序时有所选择也就不足为奇了。

Docker 并不是 Linux 用户唯一可用的选项。 Podman 提供了 Docker 的替代品,对于大多数开发人员来说,podman CLI 可以配置为docker CLI 的别名。

如果你想继续使用 Docker,有两个选择:

  • Debian/Ubuntu 上的docker.io或者 Fedora 上的docker
  • docker-ce

docker.iodocker包由各自的 Linux 发行版维护。无需添加任何额外的包存储库就可以安装它们。它们是免费和开源的。

docker-ce是 Docker 提供的一个包。该软件包可以通过为主要 Linux 发行版提供的第三方软件包存储库获得。像docker.iodocker软件包一样,docker-ce是免费的开源软件。

关于docker.io / dockerdocker-ce的底层区别有很多讨论。stack overflow上的这个问题,有近 9 万的浏览量,列出了每个包的一些利弊。

就我个人而言,我安装docker-ce包,因为它通常是最新的。

Docker 桌面适合所有人

Docker 桌面是在 Windows 10 或 11 以及 macOS 操作系统上安装 Docker 引擎的唯一途径。Docker 桌面也适用于 Linux,尽管 Linux 用户可以自由地单独安装 Docker 引擎。

Docker Desktop 是一款商业应用,要求某些团队付费。

Windows 服务器支持

Windows server 2016 和 2019 内置支持运行 Docker ,但仅支持运行 Windows 容器。

有一些在 Windows 服务器操作系统上运行 Linux 容器的建议。这篇关于服务器故障的文章提供了一个摘要和其他资源的链接。然而,我所看到的信息都没有表明 Windows server 上的 Linux 容器是生产环境支持的解决方案。

结论

开发容器化应用程序的开发者首先需要安装一个像 Docker 这样的工具。但是,虽然术语“Docker”是容器的同义词,但有许多选项可供选择,有些根本不需要任何 Docker 专用工具。

在这篇文章中,我们研究了支持容器化应用程序的各种工具和规范,并指出使用容器的开发人员可以选择的方法。

使用 Octopus Workflow Builder 了解更多信息

如果您想在 AWS 平台(如 EKS 和 ECS)上构建和部署容器化的应用程序,请尝试使用 Octopus Workflow Builder 。构建器用一个用 GitHub 操作工作流构建的示例应用程序填充 GitHub 存储库。它用示例部署项目配置了一个托管 Octopus 实例,展示了漏洞扫描和基础设施代码(IaC)等最佳实践。

愉快的部署!

ClusterIP、NodePort 和负载平衡器 Kubernetes 服务之间的区别——Octopus Deploy

原文:https://octopus.com/blog/difference-clusterip-nodeport-loadbalancer-kubernetes

Kubernetes 服务提供三种不同类型:

  • ClusterIP
  • NodePort
  • LoadBalancer

知道要配置哪种类型的服务对于允许客户端建立网络连接同时不将服务暴露给不必要的流量是至关重要的。

在这篇文章中,我将讨论这三种类型的服务以及何时应该使用它们。

ClusterIP 服务类型

下面的 YAML 定义了一个类型为ClusterIP的服务,该服务在标签app设置为web(由selector属性定义)的任何 pod 上将端口 80(由port属性定义)上的流量定向到端口 8080(由targetPort属性定义):

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080 

服务将 pod 暴露给内部网络流量。例如,您可以通过一个ClusterIP服务向其他 pods 公开一个数据库,因为外部客户机永远不应该直接访问数据库。

ClusterIP服务暴露的表面积最小,应该用于只需要暴露给集群中其他 pod 的 pod。

下图显示了同一集群中的 pod 如何通过ClusterIP服务进行通信:

ClusterIP diagram

节点端口服务类型

下面的 YAML 定义了一个NodePort服务,它将每个节点上端口 30007(由nodePort属性定义)上的流量定向到标签app设置为web的任何 pod 上的端口 8080(由targetPort属性定义):

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
      nodePort: 30007 

NodePort服务在内部公开 pod 的方式与ClusterIP服务相同。此外,NodePort服务允许外部客户通过 Kubernetes 节点上开放的网络端口访问 pod。这些端口通常在 30000-32768 的范围内,尽管该范围是可定制的。

NodePort服务对于将 pod 暴露给外部流量非常有用,其中客户端可以通过网络访问 Kubernetes 节点。例如,如果您的节点有主机名node1node2,上面的示例服务让客户机访问 http://node1:30007http://node2:30007。外部客户端连接到哪个节点并不重要,因为 Kubernetes 配置网络路由,将所有流量从任何节点上的端口 30007 定向到适当的 pods。

在实践中,我还没有看到NodePort服务在生产系统中被大量使用。不寻常的端口经常受到限制性防火墙规则的限制,并且很难理解您使用像 http://node1:30007 这样的 URL 与什么服务进行通信。NodePort服务非常适合测试,因为它们可能不需要任何额外的基础设施来将 pod 暴露给外部流量,这使得它们成为调试 pod 的快速而简单的方法。

下图显示了外部客户端如何通过由NodePort服务公开的节点上的端口与 pods 通信:

Diagram with boxes and arrows showing how external clients can communicate with pods via ports

负载平衡器服务类型

下面的 YAML 定义了一个LoadBalancer服务,它将流量从公共负载平衡器上的端口 80(由port属性定义)和内部服务定向到标签app设置为web的任何 pod 上的端口 8080(由targetPort属性定义):

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080 

LoadBalancer服务在内部公开 pod 的方式与NodePort服务相同。此外,LoadBalancer服务创建外部网络基础设施,将网络请求定向到集群中的 pod。在云平台上,如 Azure、AWS 和 GCP,外部负载平衡器通常由云提供商的现有负载平衡器服务之一提供。例如,AWS 上的一个 EKS 集群可能会创建一个弹性负载均衡器(ELB) 来将 pods 暴露给公共网络流量。

当 pod 需要通过可预测的 URL 向外部客户端公开时,或者当需要对外部客户端建立的连接进行额外控制时,服务是最佳选择。通过使用云提供商提供的现有负载平衡器解决方案,LoadBalancer服务使管理员能够配置额外的设置,如扩展、防火墙、路由等,以及以 pod 为目的地的外部流量。

LoadBalancer服务的缺点是通常会产生额外的费用。例如,如果一个 ELB 实例一天 24 小时运行,每月的成本大约为 16 美元,这还不包括任何与网络流量相关的成本。

下图显示了外部客户端如何通过由LoadBalancer服务创建的负载平衡器与 pods 通信:

Diagram showing how external clients can communicate with pods via a load balancer

结论

不同类型的 Kubernetes 服务提供了多种方式将 pod 暴露给网络流量。选择正确的服务取决于您是否需要在集群内部向能够访问非标准端口的外部客户端,或者向需要专用负载平衡器的规模和灵活性的外部客户端公开 pod。

在这篇文章中,您了解了ClusterIPNodePortLoadBalancer服务之间的区别,以及每种服务何时可以使用。

愉快的部署!

不同类型的软件测试- Octopus 部署

原文:https://octopus.com/blog/different-types-of-tests

对于软件团队来说,测试是有意义的,应该对应用程序进行缺陷筛选。但是,为什么测试对您的业务很重要,它如何适应 DevOps?

测试是连续交付的一部分,在进入下一阶段之前,确保交付管道每个阶段的质量。DevOps 是在短迭代中构建、测试和发布软件的迭代周期。全面的测试环境有助于 DevOps 循环的每次迭代增强产品的质量。一个弱的测试阶段可能意味着缺陷进展到发布,开发人员需要在产品运行时修复 bug。开发团队分属于测试领域的两个方面。

Mabel 进行的关于 DevOps 中测试状态的调查表明,自动化测试(至少 4-5 种不同类型的测试)是客户满意度的关键。 2021 年 DevOps DORA 报告显示,持续测试是成功的一个指标,达到可靠性目标的精英员工使用持续测试的可能性是其他人的 3.7 倍。

在这篇文章中,我将讨论自动化和手动测试,以及两种常见的测试类型:功能性和非功能性。

在 Octopus Deploy,我们通过提供一流的部署管理工具来帮助简化复杂的部署,该工具可与您的 DevOps 流程一起创建如下所示的部署循环:

Octopus DevOps

手动和自动测试

软件测试可以是手工的,也可以是自动的。如果你在你的设备上使用了一个应用程序并报告了一个错误,那么你已经进行了一次手动测试。自动化测试是预先编写好脚本并由机器执行的,它们将预期结果与实际结果进行比较。这两种测试方法在软件应用中都有它们的位置,但是手动测试比较慢,并且需要一个测试环境。

由于开发人员提前编写了自动化测试,在手工测试中发现的错误可以通知自动化测试来加强测试套件。当意见和细微差别起作用时,比如用户体验,手动测试是合适的。在这些情况下,自动化测试没有预先确定的检查结果。

自动化测试几乎是即时的,在运行时执行成百上千次。自动化测试检查功能,并确保每一行代码和功能都按预期工作。在 DevOps 过程中,自动化测试通过给出应用程序的测试覆盖来实现连续交付。如果您想要为您的应用程序设置一个测试覆盖率,您可以为应用程序的每个组件安装自动化测试。

当您向版本中添加新功能时,您可以运行测试来确定测试覆盖率是否已经降低。您可以使用结果来识别新版本的缺陷。自动化测试补充了连续交付开发运维策略。自动化的测试越多,应用程序在构建、测试和发布的 DevOps 循环中迭代和循环的速度就越快。

功能和非功能测试

您可以对您的应用程序执行许多测试。对测试进行分类的一种方式是功能性和非功能性。

功能测试会问这样的问题:

  • 这个按钮能用吗?
  • 一个模块与另一个模块一起工作吗?
  • 用户之旅是否从体验开始一直到结束?

维护测试检查应用程序是否保留了不同版本的所有功能。它询问应用程序中是否有任何功能在不同版本之间退化。我将维护测试包含在功能测试中,因为它与功能相关,尽管一些资料将它列为第三种类型。

非功能性测试检查系统运行的方式,而不是系统的功能。非功能性测试会问这样的问题:

  • 应用程序的安全性如何?
  • 应用程序可以处理多少负载?
  • 如果需要,应用程序可以扩展吗?

功能测试的例子

单元测试

单元测试测试单个代码单元的功能。在下面的例子中,我们测试一个函数,它是执行一项任务的代码块。

 1\. Test if a function works. This function averages the weather for the previous 14 days.
2\. Give the function the weather inputs for the previous 14 days.
3\. Check whether the expected output matches the actual output. 

集成测试

集成测试验证两个或更多模块之间的功能。这个例子测试了电子商务店面和购物车模块之间的集成。

 1\. Load the e-commerce store and add some items to the cart, and go to checkout.
2\. Check that the correct number of items are in the cart and the listed price is correct. 

烟雾测试

冒烟测试是揭示可能导致拒绝放行的故障的初步测试。

 1\. Does the web server return a 200 OK response?
2\. Can I ping the database? 

验收测试

验收测试确认您的应用程序按照需求规范运行。在这个例子中,奖励系统需要与应用程序一起工作。测试检查奖励系统的预期行为。

 1\. If a user tries to purchase a product with rewards points and they have enough points, the purchase price should be $0.
2\. If a user tries to purchase a product with rewards points and they do not have enough points, the purchase price should be $20. 

非功能测试的例子

负载和性能测试

负载和性能测试检查应用程序的速度、响应时间和资源使用等指标。

 1\. Measure the loading time of the home page and flag if it exceeds a threshold value.
2\. Measure database response time when handling 100 or more concurrent requests. Flag if response time exceeds threshold value.
3\. Under increasing load, test how many nodes an application needs to recover. 

安全测试

安全测试检查系统中与安全相关的弱点。

 1\. Scan log files for sensitive information. eg. credit card numbers or email addresses.
2\. Scanning for long running sessions as a sign that session handling isn't working as expected. 

可扩展性测试

可伸缩性测试测试与应用程序伸缩相关的问题。

 1\. Under increasing load, test how many nodes an application needs to recover.
2\. Test the length of time required for more nodes to be added and the application to recover. Chart how quickly you can expect 90% of scale up events to complete in. 

结论

测试对于 DevOps 过程是必不可少的,确保你的软件在进入下一阶段之前满足质量要求。研究表明,自动化测试是客户满意度和成功团队的重要指标。

测试可以手动运行,也可以自动运行,有两种主要的测试类型:功能性的和非功能性的。

强大的测试环境非常适合 Octopus Deploy。Octopus 负责发布和部署,并自动化 DevOps 生命周期的各个部分,使复杂的部署变得更加容易。

要了解更多关于测试的重要性,请阅读我们关于为什么您应该在部署后跟踪漏洞的帖子。

愉快的部署!

使用 Docker 作为包管理器- Octopus Deploy

原文:https://octopus.com/blog/docker-as-package-manager

自动化开发运维任务时的一个持续挑战是确保您拥有正确的工作工具。Linux 用户长期以来一直享受着从大量维护良好的软件包库中安装工具的能力,而 macOS 用户有 HomeBrew 和 MacPorts,Windows 用户有 Chocolatey 和 winget。

然而,越来越多的基于云的工具,例如特定于平台的 CLI 工具(例如 eksctl、aws-iam-authenticator、kubectl、kind 和 helm),只能通过直接二进制下载跨多个操作系统和 Linux 发行版可靠地安装。在自动化开发运维任务时,寻找和下载工具是一个难点。

Docker 通过允许从 Docker 映像运行基于 CLI 的工具来提供解决方案。

在这篇文章中,我将探讨如何使用 Docker 作为一个通用的包管理器来跨多个操作系统下载和运行许多 CLI 工具。

DevOps 完全是关于可重复性和自动化,并且经常需要使用专门的 CLI 工具编写脚本任务。许多运行脚本的环境本质上都是短暂的。示例包括根据需要创建和销毁的 CI/CD 代理、用于运行作业的 Kubernetes pods,以及根据需求扩展和缩减的虚拟机。这通常意味着脚本编写者不能假定安装了 CLI 工具,或者依赖于工具的特定版本。

正如简介中提到的,DevOps 团队需要的许多工具只提供直接的二进制下载。这意味着即使是一个简单的脚本也可能需要首先找到任何所需 CLI 工具的相应下载链接,下载它们(通常使用某种重试逻辑来处理网络不稳定性),提取或安装工具,然后最终运行它们。

由于从互联网下载文件所需的工具因操作系统而异,这一任务变得复杂。Linux 和 macOS 用户可能可以指望安装像curlwget这样的工具,而 Windows 用户可能会使用 PowerShell CmdLets。

但是如果有更好的方法呢?

Docker 作为一个通用的包管理器

如今,每个主要的 CLI 工具和平台都提供了维护良好的 Docker 映像。无论这些图片是由供应商自己发布在 Docker Hub 这样的存储库上,还是由第三方维护,比如 T2 Bitnami T3,你都很有可能找到你需要的最新版本的 CLI 工具作为 Docker 图片。

运行 Docker 镜像的好处在于,下载和执行镜像的命令在所有操作系统和所有工具上都是相同的。

要下载映像,重用任何以前下载的映像并自动重试,请运行命令:

docker pull image-name 

然后,使用以下命令执行该映像:

docker run image-name 

正如您将在接下来的部分中看到的,对于短期管理任务,可靠地运行映像还需要额外的参数,但是一般来说,对于每个工具和操作系统,您只需要知道这两个命令。与在 Windows 和 Linux 之间编写独特的脚本来从自定义 URL 下载二进制文件相比,这是一个巨大的进步。

让我们看看 helm,它是一个从 Docker 映像运行 CLI 工具的实际例子。第一步是在本地下载映像。注意这一步是可选的,因为 Docker 会在运行之前下载丢失的图像:

docker pull alpine/helm 

使用以下命令不带任何参数运行 helm。传递给docker--rm参数在完成时清理容器,这在为单个操作运行图像时是理想的:

docker run --rm alpine/helm 

这导致帮助文本被打印到控制台,就像您运行了一个本地安装的没有参数的helm版本一样。

用于 DevOps 自动化的 CLI 工具的一个常见要求是能够读取文件和目录,无论它们是配置文件、较大的包(如 zip 文件)还是包含应用程序代码的目录。

就其本质而言,Docker 容器是自包含的,默认情况下不读取主机上的文件。然而,可以使用-v参数将本地文件和目录挂载到 Docker 容器中,允许进程在 Docker 容器中运行,以读写主机上的文件。

下面的命令用一个为运行alpine/helm映像而创建的容器挂载几个共享目录。这允许 Docker 容器中的helm可执行文件访问配置设置,比如 helm 存储库。它还传递参数repo list,其中列出了已配置的存储库:

docker run --rm -v "$(pwd):/apps" -w /apps \
    -v ~/.kube:/root/.kube -v ~/.helm:/root/.helm -v ~/.config/helm:/root/.config/helm \
    -v ~/.cache/helm:/root/.cache/helm \
    alpine/helm repo list 

如果您之前没有定义任何 helm 存储库,此命令的输出将是:

Error: no repositories to show 

为了演示卷挂载是如何工作的,使用本地安装版本的helm配置一个新的 helm 存储库:

helm repo add nginx-stable https://helm.nginx.com/stable 

运行 helm Docker 映像来列出存储库,这表明它已经加载了由本地安装的helm副本添加的存储库:

NAME            URL
nginx-stable    https://helm.nginx.com/stable 

反之亦然。运行以下命令,从 helm Docker 映像添加第二个存储库:

docker run --rm -v "$(pwd):/apps" -w /apps \
    -v ~/.kube:/root/.kube -v ~/.helm:/root/.helm -v ~/.config/helm:/root/.config/helm \
    -v ~/.cache/helm:/root/.cache/helm \
    alpine/helm repo add kong https://charts.konghq.com 

然后使用以下命令列出本地安装的工具中的 repos:

helm repo list 

本地安装反映了新添加的 repo:

NAME            URL
nginx-stable    https://helm.nginx.com/stable
kong            https://charts.konghq.com 

别名 Docker 运行命令

虽然 Docker 让您可以方便地下载和运行图像,但键入docker run命令可能会变得非常冗长乏味。幸运的是,可以给docker run命令起别名,这样它就可以替代本地安装的工具。

使用alias命令将docker run映射到helm命令:

alias helm='docker run --rm -v $(pwd):/apps -w /apps -v ~/.kube:/root/.kube -v ~/.helm:/root/.helm -v ~/.config/helm:/root/.config/helm -v ~/.cache/helm:/root/.cache/helm alpine/helm' 

先前的 alias 命令仅在定义它的会话中有效。如果您注销并重新登录,别名会丢失。要使别名永久化,编辑~/.bash_aliases文件,并在新的一行中添加上面的 alias 命令:

vim ~/.bash_aliases 

在许多 Linux 发行版中,~/.bash_aliases文件是由~/.bashrc文件自动加载的。如果没有,将以下代码添加到~/.bashrc文件中:

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi 

在 Octopus 脚本步骤中使用 Docker 图像

如果您在非交互式 shell 中使用别名(比如在 Octopus 部署中运行脚本步骤),您需要通过运行以下命令来确保别名得到扩展:

shopt -s expand_aliases 

以下代码片段显示了下载 Docker 映像并设置别名的脚本步骤。日志记录级别已设置为 verbose,因此 Docker 映像下载消息不会填充部署日志。它还演示了通过-e参数将环境变量传递给docker run:

echo "Downloading Docker images"

echo "##octopus[stdout-verbose]"

docker pull amazon/aws-cli 2>&1

# Alias the docker run commands
shopt -s expand_aliases
alias aws='docker run --rm -i -v $(pwd):/build -e AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY amazon/aws-cli'

echo "##octopus[stdout-default]" 

结论

Docker 镜像为安装和运行通用 DevOps CLI 工具提供了一致且方便的方法,尤其是与编写 OS 命令从唯一的 URL 下载二进制文件相比。

在这篇文章中,我研究了如何从命令行或在脚本中运行 Docker 映像,并提供了一些技巧来允许 Docker 映像作为本地安装工具的替代运行。

了解更多信息

如果你想在 AWS 平台上构建和部署容器化的应用程序,比如 EKS 和 ECS,试试 Octopus Workflow Builder。构建器使用 GitHub Actions 工作流构建的示例应用程序填充 GitHub 存储库,并使用示例部署项目配置托管的 Octopus 实例,展示漏洞扫描和基础架构即代码(IaC)等最佳实践。

愉快的部署!

Docker 编写 RFC - Octopus 部署

原文:https://octopus.com/blog/docker-compose-naming

在 Octopus Deploy 3.5.1 build 中发布了对 Docker 引擎步骤的一些基本支持之后,我们将注意力转向了下一个抽象层次...码头工人作曲

现在已经开始为 Docker 提供一流的支持,我们仍然希望您对下一步的反馈(双关语)。

命名很难

Docker Compose 允许你通过一个docker-compose.yml配置文件管理多个容器、网络和卷。它使用一个名为的项目来隔离资源组,以允许在单个主机内管理多个环境,通过这种方式,您可以让几个 Docker 组合应用程序在给定的主机上运行,并在它们自己的网络内隔离。Multiple Docker apps per host

我们正在尝试的一个想法是在部署时根据 Octopus Deploy project + step + environment的组合来命名这些应用。这将允许部署跨 Octopus 环境启动多个 Docker Compose 应用程序,但是确保当部署发生并且我们只想更新现有服务时,只有那些容器得到更新和重新启动(根据对配置文件的更改,如映像版本更新的定义)。例如,下一个版本可能涉及更新用于 web 容器的图像版本,以及增加该应用程序中 web 容器的数量。那么理想情况下,只有相关 Docker Compose 应用程序中的 web 容器会受到该部署的影响。Docker Compose 为我们提供了这样一种能力,即针对一组容器,一次性更新它们,而不是分别执行多个停止/运行步骤,但是要实现这一点,需要一些命名约定。Compose step only updates app scoped with same namespace

另一方面,这与 Octopus Deploy 使用的标准包提取略有不同,在这种情况下,部署项目的多个版本可能同时存在于同一个环境中,因为它们只是存储在文件系统的不同位置。如果需要,潜在地向标识符提供用户提供的元素(例如频道名称)将允许类似的行为。

配置源

如上所述,Docker 编写步骤的必需元素是描述应用程序的 docker-compose.yml 文件。为了提供与 Octopus 部署包生命周期的集成,我们希望用户通过 Octopus 部署包选择过程将一个或多个映像绑定到 Docker 映像。当一个发布被创建时,在该步骤中绑定到服务的所有包版本将可供用户选择。该配置还能够与标准变量替换一起工作,以允许在部署时解析的特定于环境的配置。如果支持变量替换,你认为你会需要使用多个合成文件

也许从一个版本化的包中提取配置文件会更好(类似于来自包的脚本当前的工作方式)?在这种情况下,您可能需要提供在版本创建时选择的映像列表的选项,而不需要从配置中自动提取服务列表。

部署时构建

通过提供一种从单独的包中获取配置的机制,它可能会额外提供提供Dockerfiles的能力,该能力可用于在部署时作为docker-compose过程的一部分构建映像。我们有意避免在我们的第一个3.5.1版本中提供一个docker build步骤,因为这感觉像是一个应该在 CI 渠道的构建阶段进行的过程。单一的不可变构建工件,即映像,应该被创建一次,并在相关的环境中进行。考虑到 Octopus Deploy 和 Docker 的理念,您认为创建映像在部署过程中有用吗?

微软也落后于 Docker,并宣布在 Visual Studio 2017 中为直接部署到容器的提供一流的支持(因此仅比我们和我们的第一个 Docker 集成落后一点点;))...Docker 可能很快会在您的 CI/CD 渠道中扮演更重要的角色。

征求意见

希望你有机会尝试一下3.5.1中提供的新 Docker 步骤,感受一下我们在 Octopus Deploy 中的这些特性。最终,这项工作是为了让您在部署应用程序时更加轻松,所以您的意见最重要。

  • 你如何看待 Docker 编写组的命名空间,以便在升级时方便修改?
  • 您首选的提供配置文件的机制是什么,内联还是包源?
  • 您认为提供映像构建功能有意义吗?
  • 我们还完全错过了什么?

如果您对 Docker Compose 的外观有任何其他想法或意见,请在下面添加您的评论或评论并关注开放的 GitHub ticket #2875

集成 docker-compose 步骤和 Octopus Deploy - Octopus Deploy

原文:https://octopus.com/blog/docker-compose

Octopus working with Docker Compose illustration

从版本 3.5.0 开始,Octopus 就支持简单的 Docker 步骤,从那时起,容器的前景发生了巨大的变化。越来越多的持续部署管道使用容器,无论是标准化构建环境、测试还是运行时。尽管我们最近将注意力集中在改进我们的 Kubernetes 支持上,但是对于某些需求,一个简单的 docker-compose 文件就足够了。你只需要一把锤子,为什么还要用射钉枪呢?

随着最近在2018.8.0版本中对Run a Script部署步骤的升级,执行docker-compose任务现在可以更好地集成到您的 Octopus 部署项目中。在本帖中,我们将看看如何通过利用一些伟大的新脚本特性来轻松提供环境变量以及将图像版本绑定到 Octopus 版本。本例中的所有图片和脚本都可以在 GitHub 或 DockerHub 上找到。

从简单的作曲开始

我们想建立一个简单的网站,有一个访问者计数器(你好,1990 年的)和一个简单的消息。为此,我们将运行一个小型 python 网站,将访问者状态存储在 redis 数据存储中。将网站包装到一个容器中,并通过 docker-compose 运行它们,这意味着我们可以确保执行环境总是符合我们的期望。我不会深入讨论编写 python 脚本或为该网站构建 docker 映像的细节,但可以随意查看托管这些项目文件的 GitHub repo 上的 web 应用Dockerfile 的源代码。

在构建我们的 web 应用程序映像之后,我们可以创建一个类似于

version: '2'
services:
  web:
    image: sample-python-webapp:latest
    ports:
     - "5000:5000"
    environment:
      - text="Hello World"
  redis:
    image: redis:latest 

然后只需要运行docker-compose up就可以了,我们得到了一个很棒的网站

localhost

通过 Octopus 部署

现在我们知道我们的网站工作,我们想通过 Octopus Deploy 部署它,这样我们就可以充分利用环境进展,控制图像版本和变量注入。

docker 编写期间的自定义值

我们希望能够通过标准的 docker 变量动态传递细节,而不是将图像细节硬编码到docker-compose.yml文件中。

然后,docker-compose.yml文件看起来像这样

version: '2'
services:
  web:
    image: ${WEB_IMAGE}:${WEB_TAG}
    ports:
     - "${WEB_PORT}:5000"
    environment:
      - text=${TEXT_MESSAGE}
  redis:
    image: ${DB_IMAGE}:${DB_TAG} 

但是,如果我尝试在本地运行这个,我会得到一堆错误,因为 docker 不知道这些变量应该有什么值。我们可以通过提供一个T2 文件来解决这个问题。

WEB_IMAGE=mywebapp
WEB_TAG=1.0.0
DB_IMAGE=redis
DB_TAG=latest
TEXT_MESSAGE=I Am A Local Message
WEB_PORT=5889 

现在当我运行docker-compose up时,docker 将使用这个文件中的值。

章鱼项目

虽然没有 docker-compose 的具体步骤,但是我们可以使用最新版本的脚本步骤中提供的多包功能来提供关于我们想要用于.env文件的具体图像的信息。我们将自己调用docker-compose,所以我们将从run a script步骤开始。

由于我们想要对所有的脚本和docker-compose.yml文件进行版本化,我们将通过 GitHub feed 获取它们。我们实际上没有任何构建这些脚本所需的过程(忽略我们通过单独的过程构建的 web 应用程序映像本身),所以使用 GitHub feed 允许 Octopus 直接从源代码控制中获得我们的脚本代码。在我们的例子中,我们将使用前面提到的公共 GitHub 库,并将脚本文件设置为WebAppAndRedis/octopus_startup.sh,并传递几个参数-n "#{ProjectName}" -o。这个脚本将包含我们希望 Octopus 在部署期间运行的代码,我们将很快检查它的内容和那些参数。

script selection

这些字段下是新特性Additional Packages。让我们添加对用于这个项目的两个 docker 图像的引用。在这种情况下,我们将利用 DockerHub feed 并选择官方redis图像

redis image

我们之前使用的sample-python-webapp图像已经被推送到 OctopusDeploy DockerHub 注册表中。

redis image

在这两种情况下,我们都选择了而不是在服务器上获得它们。这意味着包的细节将作为变量提供给脚本,但是采集不会被执行。在我们的docker-compose步骤中,如果有必要的话,docker 会自动将它们拉下来,但是在某些情况下,您可能希望包文件可用,例如,您希望使用我们在第一步中选择的主包中不包含的附加脚本或工具。注意我们给这两个额外的包引用的名字(DBImageWebImage),这将被用来作为变量引用不同的包细节。

让 Octopus 变量进入 docker-compose

我们想要做的是通过.env文件向docker-compose.yml文件提供选定的图像细节。我们可以用一堆 Octopus 变量替换现有的.env文件,以便在部署时替换,但是这样我们就不能在开发期间本地运行它。相反,让我们创建一个单独的文件octopus.env

WEB_TAG=#{Octopus.Action.Package[WebImage].PackageVersion}
WEB_IMAGE=#{Octopus.Action.Package[WebImage].PackageId}
DB_TAG=#{Octopus.Action.Package[DBImage].PackageVersion}
DB_IMAGE=#{Octopus.Action.Package[DBImage].PackageId}
TEXT_MESSAGE=#{WebMessage}
WEB_PORT=#{WebPort} 

脚本步骤的另一个很棒的新特性是执行变量替换或配置转换的能力。让我们启用这个特性,并确保这个新文件octopus.env替换了变量。

variable replacement

这里是用于这个项目的其他变量。

Project variables

执行脚本

我们之前跳过了实际的脚本执行,所以现在我们知道我们将有一个 Octopus 提供的.env文件,让我们看看这个脚本实际上做了什么。

在撰写本文时,docker-compose还没有提供一个简单的方法来传递一个定制的.env文件,尽管有一个活动的 GitHub 问题,所以下面的步骤将来可能会变得简化。为了避开这个限制,我们将在部署时把octopus.env文件重命名为.env。这发生在变量替换之后,因此当我们运行docker-compose时,它将利用这些变量,而不是我们在开发时包含的默认变量。

因为我们也希望能够在本地运行和测试这个脚本,所以我们将确保.env文件更改可以配置为而不是发生,因为我们只在 Octopus 部署期间传递了-o参数。

#!/bin/bash 

 cmd="up"
 octopus=0
 project_name="MyApp"
 while getopts ":n:c:o" opt; do
  case $opt in
    o) octopus=1
    ;;
    n) project_name="$OPTARG"
    ;;
    c) cmd="$OPTARG"
    ;;
    \?) echo "Invalid option -$OPTARG" >&2
    ;;
  esac
done

if [[ $cmd = "up" ]]; then
    if [[ octopus -eq 1 ]]; then
        echo "Replacing '.env' file with '.octopus.env'"
        mv ./.env ./.env.old
        mv ./octopus.env ./.env
    fi
    #docker-compose pushes non errors onto stderr. Redirect to stdout
    docker-compose --project-name $project_name  up -d  2>&1
else
    docker-compose --project-name $project_name  $cmd  2>&1
fi 

我们现在可以用.octopus_startup.sh up.octopus_startup.sh down在本地运行这个脚本。使用-n参数传递项目名称可以设置 docker 放在容器上的名称,并允许我们在同一个服务器上运行多个实例。因为在本例中我们将在同一台机器上运行两个 Octopus 环境,所以 compose 项目名称将包含 Octopus 环境名称作为项目变量MyApp-#{Octopus.Environment.Name}

根据您的部署的简单或复杂程度,您的执行脚本可能需要与这个脚本有很大的不同,因此将它作为说明预期行为的指南。核心目标是替换.env文件并调用docker-compose up命令。例如,您可能希望增加特定容器的实例数量,或者执行额外的配置,如设置卷装载等。

我们在 SSH 目标上运行这个脚本,所以我使用了 bash 脚本,但是在 GitHub repo 中也有类似的 PowerShell 脚本。

创建发布并部署

现在一切就绪,我们可以部署我们的项目了!当我们创建一个版本时,我们可以看到我们能够选择 3 个不同的包,尽管我们只有一个步骤!

【T2 Create Release

如你所见,我们可以选择包含 GitHub 中所有脚本文件的包的版本,以及 DockerHub 中包含的两个图像的版本。

然后,我们可以将我们的发布部署到开发中

dev

和分级

staging

如果我们想更新传递到容器中的问候文本,我们只需更新项目变量并再次部署即可!如果我们推出新版本的 web 应用程序,我们只需创建一个新版本,指定该映像的最新版本并进行部署!

Docker-Compose 和章鱼

如上所示,使用脚本步骤中可用的新的多包选择为部署自动化提供了全新的可能性,包括更容易的 docker-compose 部署。要将 docker-compose 集成到 Octopus 流程中,请选择每个图像作为附加的脚本步骤包,并使用.env文件来提供需要更改的值。

希望这篇文章能给你一些关于从哪里开始 docker-compose 部署的想法。我们将继续向 Octopus Deploy 添加额外的功能,以使部署容器变得尽可能简单,这样您就可以回到实际编写代码的工作中来了!看看我们的 Kubernetes 支持在最新的大版本2018.8.0中作为 alpha 版本提供!

Octopus 部署中的停靠引擎- Octopus 部署

原文:https://octopus.com/blog/docker-engine-in-octopus-deploy

Octopus Deploy 中现在提供了基本的 Docker 引擎命令

在我们谨慎地提出我们对 Octopus 如何与 Docker 整合的想法后,来自社区的普遍反馈虽然积极,但似乎有点不确定。这可能部分是因为 Docker 对我们大多数人来说是一项相当新的技术。净客户群,也是由于一个的案例,看到就知道是否管用。由于这个原因,我们发布了新的 Docker 功能作为早期访问功能,但捆绑在最新的 Octopus Deploy 3.5.1 版本中。

在你的八达通服务器上启用 Docker

因为我们想清楚地表明这些功能仍在开发中,所以我们默认隐藏了它们,它们只能通过切换 Docker 功能标志来启用。一旦这个特性被打开,Docker 步骤就可以在项目中使用,Docker 注册中心也可以作为外部提要来创建。一旦特性完全稳定下来,我们希望它们在默认情况下是可见的,并且切换被移除。Enable Docker Feature Flag

值得注意的是,这是因为在下一个版本中,Docker 特性集可能会引入一些突破性的变化。我们希望用户和早期采用者能够接触到这个版本,一起和章鱼& Docker 一起玩,看看什么有用,什么没用。运行容器如何适应您的发布周期?

码头进料

3.5.1 支持新的提要类型,允许您在发布创建时从 Docker 注册表中选择 Docker 映像,就像您习惯于使用 NuGet 提要一样。我们现在有一些其他有趣的想法,提要不一定需要成为 NuGet 库...所以看好这个空间;)

了解更多关于 Docker 注册表提要的信息。

码头台阶

到目前为止,我们正在为docker rundocker stopdocker network create提供一流的支持。我们的目标是从支持 Docker 生态系统的基本构建模块开始,然后在它们之上构建并覆盖顶层抽象,如 Docker Compose 和 Docker Swarm。最终,我们还计划覆盖与 Kubernetes、Apache Mesos 以及 AWS 和 Azure Container Services 等云选项的集成。

查看我们的指南,开始一个基于 Docker 的基本项目。Docker Step

限制

在此版本中,Docker 步骤有一些已知的限制;

  • Docker Hub 的 API 和官方注册 API 规范之间有一些细微的差别。此外,由于 Docker Hub 与普通注册表相比暴露了关于图像的额外信息(例如,它们是官方图像?它们是公开的还是私人的?)信息的可访问性各不相同。虽然公共的、官方的存储库在这个版本中运行良好,但是非官方图片的标签不会返回,私有的存储库也不会暴露。建立自己的存储库非常简单(当然是使用容器!)目前,如果试图从非官方 Docker Hub 映像运行容器,我们建议使用私有存储库。

  • Windows 上的 Docker 仍然有一些问题需要解决,特别是在网络和容量方面,所以目前我们建议在 Linux 目标上进行尝试。这是一个我们确实希望在未来版本中得到同等功能的领域,但是这很大程度上依赖于 Docker 和微软来实现。

你觉得怎么样?

正如已经提到的,Docker 到 Octopus Deploy 的集成将经历几个阶段,在每一步,我们都希望确保我们正在构建您完成工作所需的东西。这个第一阶段有望提供这个集成看起来像什么的指示,即使有一些改进要做。今天下载 Octopus Deploy 的最新版本,让我们知道你的想法!

Docker on Windows 和 Octopus Deploy - Octopus Deploy

原文:https://octopus.com/blog/docker-windows-octopus

更新 2016-08:自从本文撰写以来,Docker 与 Windows 的集成已经取得了进展。查看我们的 RFC 帖子,了解关于我们在 Octopus Deploy 中发展这一特性的令人兴奋的更新!

今天,Gu 宣布微软与 Docker 合作,将 Docker 引入 Windows

微软和 Docker 正在将开源的 Docker 引擎与下一个版本的 Windows Server 集成在一起。此版本的 Windows Server 将包括新的容器隔离技术,并支持同时运行这两种技术。NET 和这些容器中的其他应用程序类型(Node.js、Java、C++等)。开发人员和组织将能够使用 Docker 为 Windows Server 创建分布式的、基于容器的应用程序,这些应用程序利用了 Docker 的用户、应用程序和工具生态系统。

多刺激啊!在过去的几个小时里,我一直在钻研 Docker,以及这一声明对的未来可能意味着什么。NET 应用程序部署。以下是我目前为止的想法。

容器与虚拟机

除了 Scott 的帖子,我找不到太多关于 Windows Server 中容器支持的信息,所以我先声明这只是推测,纯粹是假设它们的工作方式类似于 Linux 容器。

曾几何时,你只有一台物理服务器,运行着包含上百个网站的 IIS。现在,随着虚拟化和云计算的兴起,我们倾向于拥有一台物理服务器,运行几十个虚拟机,每个虚拟机运行一个应用程序。

我们为什么要这么做?这真的是关于孤立。每个应用程序可以在不同的操作系统上运行,具有不同的系统库、不同的修补程序、不同的 Windows 功能(例如,安装了 IIS)、不同版本的。NET 运行时,等等。更重要的是,如果一个应用程序出现严重故障,导致操作系统崩溃,或者操作系统需要重启进行更新,其他应用程序不会受到影响。

过去,我们开始在一个版本的。NET framework(比如说 3.5),却被告知没有人会把 3.5 放在生产服务器上,因为在那个服务器上有 49 个使用 3.0 的应用程序可能会崩溃,并且要花很长时间来测试它们。虚拟化让我们摆脱了这些限制。

从部署自动化的角度来看,构建服务器编译代码,并生成准备部署的包。Octopus Deploy 服务器将包推送到远程代理(即触手)进行部署。

Deployment today with Octopus on virtual machines

所以,隔离很好。但是主要的缺点是我们实际上只有一台物理服务器,每台服务器都运行相同操作系统内核的多个副本。这是一个真正的遗憾,因为这个操作系统是为多任务设计的服务器级操作系统。事实上,假设您在每个虚拟机上运行一个主应用程序,那么您的物理机器实际上运行的操作系统比运行的主应用程序还要多!

容器是相似的,但是不同的:只有一个内核,但是每个容器保持相对独立。有很多关于安全容器与虚拟机相比如何的争论,所以对于共享相同硬件的完全不同的客户来说,VM 可能总是更受欢迎。然而,假设存在基本的信任,容器是一个很好的中间地带。

什么是 Docker 页面很好地概述了为什么容器不同于虚拟机。我还没有看到 Windows Server 中的容器是如何工作的,但是在这篇文章中,我假设它们是非常相似的。

Docker 适合哪里

Docker 在这些容器之上提供了一个层,使得构建在容器中运行的图像以及共享这些图像变得更加容易。使用基于文本的Docker 文件定义 docker 图像,该文件指定:

  • 从基础操作系统映像开始
  • 准备/构建映像的命令
  • 图像“运行”时要调用的命令

对于一个 Windows Dockerfile 文件,我想它看起来会像这样:

  • 从 Windows Server 2014 SP1 基础映像开始
  • 安装。NET 4.5.1
  • 安装 IIS 并启用 ASP.NET
  • 复制动态链接库,CSS,JS 等。ASP.NET web 应用程序的文件
  • 配置 IIS 应用程序池等。并启动网站

因为它只是一个小的文本文件,所以您的 Dockerfile 可以提交给源代码控制。然后从命令行构建一个“映像”(即执行 Dockerfile),它将下载所有的二进制文件并创建一个磁盘映像供以后执行。然后,您可以在不同的机器上运行该映像的实例,或者通过 Docker 的 Hub 与其他人共享它。

Docker 和使用这样的容器的巨大优势不仅仅在于节省内存/CPU,还在于使您在测试环境中测试的应用程序更有可能在生产环境中实际工作,因为它将以完全相同的方式配置——它是完全相同的映像。这是一件非常好的事情,将构建二进制文件一次发挥到了极致。

这对章鱼意味着什么

首先,记住 Octopus 是一个部署自动化工具,我们特别适合那些不断构建同一应用程序新版本的团队。例如,一个团队在两周的冲刺中构建一个内部 web 应用程序,每两周部署一个新的应用程序版本。

考虑到这一点,Docker 和 containers 可能以几种不同的方式与 Octopus 一起使用。

方法 1: Docker 是一个基础设施问题

这可能是最基本的方法。基础架构团队将维护 docker 文件,并根据这些文件构建映像,然后在配置新服务器时部署它们。这将保证无论他们使用哪个托管提供商,服务器都有一个共同的基线——相同的系统库、服务包、启用的操作系统特性,等等。

该图像将简单地包含我们的触手服务,而不是将应用程序作为图像的一部分。结果看起来类似于 Octopus 现在的工作方式,事实上不需要对 Octopus 做任何修改。

Octopus/Tentacle in a world of Docker

这有利于加快应用程序部署——我们只是推动应用程序二进制文件,而不是整个映像。这仍然意味着应用程序相互隔离,几乎就像在虚拟机中一样,没有开销。然而,它确实允许 cruft 随着时间的推移在图像中累积,所以它可能不是 Docker 的一个非常“纯粹”的用法。

方法 2:为每个部署构建一个新的映像

这种方法非常不同。我们只需要在物理服务器上有一个,而不是有很多触手的副本。在部署时,我们会创建新的映像并在 Docker 中运行它们。

  1. 构建服务器构建代码,运行单元测试,等等。并创建一个 NuGet 包
  2. 包中包含一个 Dockerfile 文件,其中包含构建映像的指令
  3. 在部署期间,Octopus 将 NuGet 包推送到远程机器
  4. 触手运行docker build来创建图像
  5. 如果实例正在运行,Tentacle 会停止它,然后使用新的映像启动新的实例

这样做的缺点是,由于我们每次都构建不同的图像,我们失去了 Docker 的一致性;每个 web 服务器的配置可能会稍有不同,这取决于当时各种库的最新版本。

从好的方面来看,我们确实获得了一些灵活性。每个应用程序可能有不同的web.config设置等。,Octopus 可以在文件放入映像之前更改这些值。

方法 3:每次发布映像

更好的方法可能是在过程的早期构建 Docker 映像,比如在构建结束时,或者在 Octopus 中第一次创建发布时。

Docker images in Octopus

  1. 构建服务器构建代码,运行单元测试,等等。
  2. 构建服务器(或者可能是 Octopus)运行docker build并创建一个映像
  3. 图像被推送到 Octopus 或 Docker Hub
  4. Octopus 将该图像部署到远程机器上
  5. 如果实例正在运行,Tentacle 会停止它,然后使用新的映像启动新的实例

这种方法似乎最适合 Docker,并在环境之间提供了更多的一致性-生产将与 UAT 相同,因为它在生产中运行的映像与在 UAT 运行的映像完全相同。

有一个问题:我们将如何处理配置变更?例如,在 UAT 和生产环境中,我们将如何处理不同的连接字符串或 API 键?请记住,这些值的变化速率往往不同于应用程序二进制文件或其他将在映像中拍摄快照的文件。

在 Docker 世界中,这些设置似乎是通过在启动映像实例时将环境变量传递给docker run来处理的。虽然节点或 Java 开发人员可能习惯于使用环境变量。NET 开发人员很少使用它们进行配置——我们期望从web.configapp.config获得设置。

还有一些其他的复杂性;目前,在部署 web 应用程序时,Octopus 会将新版本与旧版本并行部署,对其进行配置,然后切换 IIS 绑定,从而减少机器的整体停机时间。使用 Docker,我们需要停止旧的实例,启动新的实例,然后配置它。除非我们每次都用不同的配置构建一个新的映像(方法 2),否则停机时间将很难管理。

八达通还会增值吗?

是的,当然!😃

Docker 使得打包应用程序和运行它所需的所有依赖项变得非常容易,而且操作系统提供的容器有助于实现高度隔离。Octopus 不是关于单个应用程序/机器部署的机制(触手在这方面有所帮助,但这不是 Octopus 的核心)。章鱼是关于整个配器的。

Octopus 提供价值的地方是涉及多台机器或多种应用程序的部署。例如,在将新的 web 应用程序映像部署到 Docker 之前,您可能需要备份数据库。然后只将它部署到一台机器上,暂停一下进行手动验证,然后转移到其余的 web 服务器上。最后,为不同的应用程序部署另一个 Docker 映像。这些步骤的顺序很重要,有些是并行的,有些是阻塞的。Octopus 将提供这些高级编排能力,无论您是部署 NuGet 包、Azure 云包还是 Docker 映像。

Azure 云服务项目的未来?

说到 Azure 云包,它们还会有意义吗?

这里有一些相似之处。有了 Azure,就有了网站(只需推送一些文件,它就会在现有的虚拟机上为您托管),或者您可以供应整个虚拟机并自己管理它们。中间是云服务,即 web 和工作者角色,它涉及到在每次部署时配置一个新的虚拟机,并依赖于打包在一起的应用程序和操作系统设置。老实说,在 Windows Docker 的世界里,很难看到这类包有任何用处。

结论

对于 Windows 来说,这是一个非常令人兴奋的变化,这意味着我们在 Windows 中看到的一些其他变化开始融合在一起。Docker 在很大程度上依赖于 Linux 生态系统中的其他工具,比如包管理器,来配置实际的映像。在 Windows 世界中,直到最近的 OneGet 才出现这种情况。PowerShell DSC 也很重要,尽管我确实觉得 sytax 对它来说还是太复杂了,无法真正被采用。

Octopus 将如何适应 Docker?时间会证明一切,但是正如你所看到的,我们有几种不同的方法可以采用,其中#3 是最有可能的(已经支持#1)。随着下一款搭载 Docker 的 Windows 服务器即将上市,我们将密切关注它。

DORA metrics-devo PS 实践和业务成果之间的预测链接- Octopus Deploy

原文:https://octopus.com/blog/dora-metrics-devops-business-outcomes

成功变革的关键是衡量和理解正确的事情关注能力,而不是成熟度。"

摘自《加速》,作者 Nicole Forsgren、Gene Kim 和 Jez Humble,2018 年。

2009 年,受约翰·奥斯鲍和保罗·哈蒙兹的演讲《速度》的启发,帕特里克·德博伊斯组织了一次会议。为了向约翰和保罗表示敬意,帕特里克将他的会议称为“德沃普斯日”。这样做,他无意中给了一个广泛而分散的极客联盟一个统一的名字和身份。“DevOps”现在是一个东西…但众所周知,它很难定义。

一个充满激情、多元化的技术人员社区汇聚到了一起。他们就一些宽泛的想法(涵盖各种主题)达成一致,但努力将它们作为一个单一的事物简明地表达出来。很难将这么多关于文化、自动化、精益 It、指标和共享的想法整齐地结合到一个简短而集中的电梯演讲中。

更糟糕的是,公众的看法是…复杂的。对一些人来说,这个羽翼未丰的运动显得幼稚而危险。这些 DevOps 嬉皮士积极宣传开发者应该直接投入生产!在早期,没有多少“严肃”企业的高层人士认真对待这些 DevOps 先驱。

像《精益创业》这样的书挑战了既定的 IT 项目管理教条。像“快速移动和打破东西”这样的咒语似乎是故意与重视安全和可靠性的人作对。“NoOps”亚文化似乎呼吁大规模解雇整个功能。Clickbait-y 博文明确声称“数据库管理员死了”。

DevOps 本该团结我们。哪里出了问题?

由于 DevOps 很难定义,甚至更难衡量,很难提出令人信服的支持或反对理由。当然,DevOps 的支持者可以对他们最喜欢的 DevOps 案例研究大放厥词:Flickr、Etsy、网飞,等等……但另一方会理所当然地指责他们挑肥拣瘦,并抛出他们自己的例子,如阿波罗计划,或任何其他不能允许失败的软件系统(如航空航天或医疗保健)。

这对我们毫无益处。我们需要清晰。我们需要数据。我们需要一点科学的严谨。

德文郡报告

2012 年,在 Puppet,岚娜·布朗认识到了这一需求,并开始编写年度 DevOps 报告。后来她又请来了其他人,包括吉恩·金、妮可·福斯格伦和杰斯·亨布尔。(好一个团队!)

妮可,博士,有一些严肃的研究证书在 2013 年至 2017 年领导这项研究。每年,该团队都会调查全球数万名技术人员,涵盖不同的工作岗位和行业领域。他们检查结果,找出结论,并公布数据和他们的发现。

他们提升了关于 DevOps 的讨论。这不再是部落价值信号的问题了。我们有冰冷、坚硬的数据。我们可以谈论事实而不是观点。我们可以谈论全球趋势,而不是个案研究。

最重要的是,我们可以权威地向人们展示金钱。

2018 年,妮可与 Gene 和 Jez(他们现在已经组成了 DORA (DevOps 研究和评估)编写了 Accelerate 。在这篇文章中,他们回顾了 2013-17 年报告中的数据,并解释了数据揭示的内容。

他们的结论是惊人的。

如何衡量科技领域的表现

Nicole、Gene 和 Jez 想了解为什么有些团队比其他团队表现得更好。为此,他们首先需要一个标准来衡量 IT 团队的“绩效”。这不是一个小壮举。作者讨论了各种传统度量的挑战,例如代码行数、故事点和利用率等。仅仅根据个人或团队投入的工作来定义他们的表现是有问题的。

相反,作者选择关注结果而不是产出。当他们这样做时,他们注意到了 4 个具体指标的独特之处,这些指标涵盖了性能和稳定性的平衡。这些指标被称为“ DORA 指标”:

  • 部署频率
  • 研制周期
  • 平均恢复时间(MTTR)
  • 变更失败百分比

当团队表现更好时,特别是对照这些指标,他们会看到业务成果中独特的、具有统计意义的、可预测的改进,包括:

  • 收益性
  • 市场占有率
  • 生产力

这种联系不仅仅在“科技公司”(那些以软件产品闻名的公司)中观察到。所有商业领域都是如此。到 2018 年,高性能的技术团队在每项业务中都提供了竞争优势。此外,多拉继续讨论了其他“非营利”组织,在这些组织中,积极的结果不仅仅由银行存款余额来定义。他们再次发现 DORA 指标是成功的可靠预测指标。

现在每个公司都是软件公司。

如果你想让你的股东、高管或关键利益相关者参与你的 DevOps 转型,你需要让他们阅读 Accelerate

为什么 DORA 指标有效?

这里面有一个古老的神话:出于速度、质量和成本的考虑,你必须选择两个。我是来告诉你那是垃圾。

DORA 指标很有趣,因为它们促进了各种正反馈循环,同时强化了关于速度和质量的良好实践。这种结合允许团队更快地交付质量更好的软件。(因此,花费更少。)

部署频率

正如查克·罗西在脸书观察到的:“如果我们想要更多的变化,我们需要更多的部署。”

Chuck 面临着加快开发速度的压力。为了实现更多的变化,脸书的部署变得越来越大,越来越复杂。然而,随着部署的增长,它们变得越来越不可靠。当他们失败时,他们失败得很惨。找到问题就像在数据中心里大海捞针。脸书未能通过增加部署规模来实现其生产力目标。

如果目标是发布更多的变更,那么“向外扩展”部署就很重要,而不是“向上扩展”。

通过从根本上提高部署频率,而不是部署规模,脸书取得了更大的成功。他们能够交付更多的变更,同时遭受更少的重大部署失败。当出现问题时,诊断起来更容易,修复起来也更快。

他们了解到生产力是部署频率的函数,而不是部署规模的函数。

然而,为了从根本上增加部署频率,我们需要从不同的角度考虑交付过程(在组织层面)。如果每次部署都需要两周的测试周期、过度官僚化的审查流程和 48 小时的停机时间,我们就无法一周执行多次部署,更不用说一天 10 次部署了!(就像约翰和保罗在《速度》中介绍的那样。)

如果目标是增加部署频率,我们需要了解交付时间。

研制周期

提前期是一个有内涵的术语。Accelerate 的作者以一种特定的方式定义了它。

他们在与选择建造什么(他们称之为“模糊前端”)相关的创造力、创新、研究和决策与交付它所需的实际工程工作之间划了一条线。在 Accelerate 中,交付周期被定义为从开发人员开始某项工作到该工作在生产中交付(和验证)之间的时间。

(他们明确地计算一些 bug 修复或特性请求在更高优先级的 JIRA 票的长尾后面等待的时间量。)

由于表现差的团队以月为单位来衡量交付周期,而表现好的团队以小时为单位来衡量交付周期,对于两个阵营中的人来说,想象另一个团队是如何在不破坏东西的情况下交付软件的是令人难以置信的。对于表现不佳的人来说,他们的很多长时间都花在了测试、批准和验证上,所以他们很自然地会认为加快速度需要牺牲质量或安全。

然而,对于大多数以前没有认真考虑过交付时间的组织来说,他们可能会通过寻求理解最长等待、延迟和错误发生在哪里,然后做出改变或自动化步骤来避免/减少它们,来看到大的改进。通过将大批量的变更分解成更小的、可独立交付的批量,他们将会看到另一个巨大的提升。这些不仅交付起来更快更安全,而且在一些更广泛的史诗完成之前,如果需要的话,在不牺牲当前进展的情况下,更容易进行调整。

持续的短交付时间不是匆忙的开发工作或跳过测试阶段的结果。它们是更聪明的团队工作和更好的自动化工作的结果。它们导致代码被及时地审查,错误被更快地捕获,质量提高,部署更安全,以及更好的灵活性。

平均恢复时间(MTTR)

欣赏 MTTR 的最佳方式是将其与 MTBF(平均无故障时间)进行对比。

对 MTTR 的关注清楚地表明,我们更关心减少失败的影响,而不是完全避免失败。这并不是说我们不在乎避免失败。我们当然知道。(稍后将详细介绍。)我们只是更关心我们失败后的恢复能力。我们认识到,应该避免显著降低部署频率和交付时间的安全措施,因为它们会产生自身形式的系统风险。(更长的交付时间、更大的批量、更高风险的部署等。)

断然: Accelerate 已经证明了朵拉指标的重要性。(而 MTBF 没有。)

如果我们的故障在几分钟内得到修复,如果它们只影响我们用户的极小一部分,如果所有的数据都可以快速恢复……如果我们时不时地犯一个错误,真的有那么可怕吗?难道我们不应该把更多的精力放在从失败中恢复的能力上,而不是追求抓住每一个错误这个不可能的目标吗?

当我们展示 MTTR(而不是 MTBF)方面的持续改进时,就更容易证明减少官僚主义的合理性。这缩短了交付时间,提高了部署频率,减小了部署规模。这将带来更安全的部署,以及更好的 MTTR。

我们将恶性循环转化为良性循环。

更改失败百分比

测试是 DevOps 中经常被遗忘的部分。没有适当测试的持续部署是一种比以前更快地将 bug 部署到产品中的可靠方法。

虽然我们对 MTTR 而不是 MTBF 的关注表明我们接受失败会发生,但这并不意味着我们对失败感到高兴。DevOps 是关于构建质量的,就像它是关于部署小型和经常性的一样。我们希望我们的部署枯燥乏味,而不是扣人心弦。我们希望确信它们会起作用。

但是我们需要用快速检查代替慢速检查。

我们需要投资自动化单元测试、集成测试、模拟部署和冒烟测试。我们需要投资于频繁的、快速的、小型的代码评审,而不是缓慢的、由委员会分批进行的评审。毕竟,一次审查的代码越多,可能出现的问题就越少。

注意:虽然我们试图减少部署失败的可能性,但我们并不试图减少部署失败的总数。这可能会发生,但这不是我们的首要任务。担心失败的总数是在增加还是在减少是一种干扰。

如果这听起来有些鲁莽,请考虑以下哪家公司提供更高质量的产品:

A 公司(每季度部署) B 连(每天部署 10 次)
2021 年部署 4 架 2021 年部署 3650 人
50%的故障率 1%的故障率
MTTR:两天 MTTR: 1 小时
2 次故障 x 48 小时恢复时间= 96 小时停机时间 36 次故障 x 1 小时恢复时间= 36 小时停机时间

B 公司遭受更频繁的失败。然而,它可能被用户认为是更可靠的服务。公司 A 的停机时间大约增加了 3 倍,我们将在未来几年内记住这些令人沮丧的多日停机。

通过将对 MTTR 的关注(减少失败的影响)与共同努力提高部署的可靠性(由变更失败百分比定义)结合起来,在提高质量的同时从根本上提高部署频率是切实可行的。(即使失败的总数实际上增加了。)

如何提高性能

当然,我们应该跟踪和报告我们在 DORA 指标方面的进展。任何使用自动售票系统和发布协调工具(如 Octopus Deploy)的人都可能获得他们需要的大部分数据。

想象一下,每个人都可以访问一个仪表板,报告每个产品或团队的 DORA 指标。团队将会受到激励去模仿表现最好的人的做法。管理层将会看到哪些团队最需要他们的支持。快乐的日子。

成功变革的关键是衡量和理解正确的事情,关注能力——而不是成熟度。"

摘自《加速》,作者 Nicole Forsgren、Gene Kim 和 Jez Humble,2018 年。

然而,Accelerate 不只是给我们一个记分牌。他们的研究还强调了一组 24 项实践能力,这些能力已被证明可以提高绩效。列出的 24 项功能大体上符合 DevOps 爱好者多年来推广的实践。

老实说,自从 Accelerate 之后,“DevOps”这个词就不那么重要了。这 24 种能力已经成为公认的良好实践的默认约定。已经证明它们是有效的。

列表中的源代码控制、自动化部署和持续集成特性可能不足为奇。事实上,他们是前三名。但这是一个很长的列表,而且不是按优先级排序的。Accelerate 接受了这样一个事实,即开发运维不仅仅是部署管道。

能力列表很长,令人望而生畏。没关系。一个人不可能负责实施所有的变革,任何试图一次进行太多变革的组织都不太可能成功。不要问自己“我要花多长时间才能完成这些?”相反,选择从哪里开始。

根据我的经验,让一个跨职能团队花几个小时在白板上绘制 【价值流】 是很好的第一步。突出那些容易造成最多延迟或失败的领域,可以将注意力集中在首先要解决的最重要的问题上。同样令人欣慰的是,您已经完成了研究,并且能够对修复一些瓶颈将如何影响您的 DORA 指标做出一些估计。

说到这里,Gene Kim 在超越凤凰计划的中给了我们一个很好的指示。从个人经验来说,他的观察反映了我职业生涯中对数百名客户的观察:

我发现令人惊奇的是,当一个组织从几个月,甚至几个季度的代码部署交付时间减少到几分钟时,约束以一些相当可预测的方式移动。"

吉恩·金,《超越凤凰计划》,2018 年。

他接着按顺序列出了以下瓶颈:

  1. 环境创造
  2. 部署
  3. 测试
  4. 体系结构
  5. 新产品创意

假设您的价值流映射练习返回类似的结果,从投资于使这些任务更容易、更快、更可靠的能力开始可能是一个好主意。可能的结果是提高 DORA 指标分数。

我们知道,强大的 DORA metrics 得分预示着商业上的成功。

行动的号召

你已经读到这里了。要么我让你相信了 DORA 指标的价值,要么我没有。

如果你不相信:

我鼓励你去读加速

事实上,只需阅读第 1 部分。目前,这是唯一重要的一点。

回到疫情之前,作为一名顾问,我经常开车。我在结束咨询工作开车回家的路上,用有声读物的形式听完了第 1 部分的全部内容。只花了大约一个小时。(以双倍速度。我现在觉得其他事情都很慢。)Nicole 等人简明扼要地阐述了我一整天都在目睹的问题,但我很难向我的客户解释清楚。

我一到家就订购了一份硬拷贝,准备第二天送达。

如果 Nicole 的研究是正确的(我从未见过任何严肃的学术尝试来挑战她的方法或发现),我保证 Accelerate 的第 1 部分值得您花一两个小时的时间。

如果你确信:

我要求你回答以下问题:

  • 您的团队在 DORA 指标上表现如何?
  • 在接下来的几年里,您计划如何根据 DORA 指标跟踪您的绩效?
  • 你打算做什么改变来推动改进?

Octopus 中的 DORA 度量

DORA Metrics and DevOps Insights in Octopus

Octopus Deploy 2022.3+包括对内置 DORA 指标的 DevOps Insights 的支持。该内置报告通过呈现 4 个关键的 DORA 指标,让您更好地了解贵公司的开发运维绩效:

  • 部署提前期
  • 部署失败率
  • 部署频率
  • 平均恢复时间

这些指标有助于您做出明智的决策,以改进和庆祝您的成果。

项目洞察适用于所有 Octopus 项目,包括现有项目。

空间层次洞察可用于企业客户的空间层次,并涵盖该空间中的所有项目。

空间级洞察可通过 Insights 选项卡获得,并为跨一组项目、环境或租户的更复杂场景提供可操作的 DORA 指标。这使经理和决策者能够根据他们的业务环境,如团队、组合或平台,更深入地了解他们组织的 DevOps 性能。

空间层面的见解:

  • 汇总整个空间的数据,以便您可以比较和对比项目间的指标,从而确定哪些可行,哪些不可行
  • 为更好的决策提供信息:发现问题,跟踪改进,庆祝成功
  • 根据数据显示的实际情况,帮助您量化 DevOps 性能

这些指标一起帮助您在您的项目和投资组合中鉴定您的 DevOps 性能的结果。

在我们的文档中了解关于 DevOps Insights 的更多信息。浏览 DevOps 工程师手册了解更多关于 DevOps 和 CI/CD 的信息。

愉快的部署!

不支持的 Windows 和版本。网络-章鱼部署

原文:https://octopus.com/blog/dotnet-and-windows-ciphers

如果你在跑步。在不支持的 Windows 版本上运行. NET 应用程序时,您可能会惊讶地看到类似Authentication failed这样的错误,因为您运行的软件或其配置方式似乎没有任何变化。为了理解这些错误,我们需要深入了解 Windows 支持的密码套件,因此也需要了解。NET 应用程序。

在这篇博文中,我诊断了一个在不受支持的 Windows 版本上通过. NET 应用程序进行 HTTPS 连接失败的例子。

示例应用程序

我们的示例应用程序使用HttpClient类对作为第一个参数传入的 URL 执行 HTTP GET 请求:

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace DotNetHttpClientExample
{
    class Program
    {
        static async Task<int> Main(string[] args)
        {
            using var client = new HttpClient();
            var result = await client.GetAsync(args[0]);
            Console.WriteLine("Result was " + result.StatusCode);
            return (int) result.StatusCode >= 200 && (int) result.StatusCode <= 299 ? 0 : 1;
        }
    }
} 

为了演示一个在旧版本 Windows 上失败的网站的例子(在撰写本文时),我们将把 URLhttps://getambassador . io传递给它。

在我的 Windows 10 机器上,应用程序产生了预期的输出Result was OK。但是,在旧版本的 Windows(如 Windows 7)上,结果会出现以下异常:

Unhandled exception. System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: Authentication failed because the remote party sent a TLS alert: 'HandshakeFailure'.
 ---> System.ComponentModel.Win32Exception (0x80090326): The message received was unexpected or badly formatted.
   --- End of inner exception stack trace ---
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[]reAuthenticationData, Boolean isApm)
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
   at DotNetHttpClientExample.Program.Main(String[] args)
   at DotNetHttpClientExample.Program.<Main>(String[] args) 

Authentication failed这样的消息表面上看没有多大意义,因为我们没有在这个网络请求中传递任何凭证。那么,为什么这个示例应用程序只能在一个 Windows 版本上运行,而不能在另一个版本上运行呢?

匹配网站和操作系统之间的密码

使用像 ScanSSL 这样的工具,我们可以询问网站,看看它会接受哪些密码。结果显示了一个非常有针对性的密码列表:

 Supported Server Cipher(s):
Preferred TLSv1.3  256 bits  TLS_AES_256_GCM_SHA384        Curve 25519 DHE 253
Accepted  TLSv1.3  256 bits  TLS_CHACHA20_POLY1305_SHA256  Curve 25519 DHE 253
Accepted  TLSv1.3  128 bits  TLS_AES_128_GCM_SHA256        Curve 25519 DHE 253
Preferred TLSv1.2  256 bits  ECDHE-RSA-AES256-GCM-SHA384   Curve 25519 DHE 253
Accepted  TLSv1.2  256 bits  ECDHE-RSA-CHACHA20-POLY1305   Curve 25519 DHE 253
Accepted  TLSv1.2  128 bits  ECDHE-RSA-AES128-GCM-SHA256   Curve 25519 DHE 253 

这里报告的密码名称基于 OpenSSL。Windows 引用的密码使用 IANA 命名约定。要在两者之间转换,使用https://testssl.sh/openssl-iana.mapping.html处的表格。这为我们提供了以下 IANA 密码名称:

  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • TLS_AES_128_GCM_SHA256
  • TLS _ ECD he _ RSA _ WITH _ AES _ 256 _ GCM _ sha 384
  • TLS _ ECD he _ RSA _ WITH _ AES _ 128 _ GCM _ sha 256

微软维护着文档,其中列出了当前和以前版本的 Windows 支持的所有密码。浏览列表,不支持的 Windows 版本(如 Server 2012 和 8.1)不会列出网站接受的任何密码。因为。NET 应用程序依赖于底层操作系统公开的密码,我们的示例应用程序无法建立安全的 HTTPS 连接。

为什么你的浏览器仍然工作

人们很容易认为,因为 web 浏览器可以成功打开网站,所以所有应用程序都应该可以工作。然而事实并非如此。像 Chrome 和 Firefox 这样的浏览器维护和发布自己的密码。这意味着如果你的浏览器是最新的,它可能会包括建立大多数 HTTPS 连接所需的现代密码。

像 Go 和 Java 这样的平台也有自己的密码,所以用这些语言编写的应用程序在旧版本的 Windows 上运行时可能支持新的密码。

。然而,NET 应用程序依赖于操作系统提供的密码,而将新密码装入操作系统的唯一方法是通过微软的补丁。不受支持的 Windows 版本通常不会收到这些补丁,因此随着时间的推移,越来越多的网站将停止使用这些补丁。NET 应用程序。

结论

不建议运行不支持的 Windows 版本,这通常用“这不安全”这样的模糊陈述来解释。虽然这种说法是正确的,但这篇博客文章展示了一个具体的例子,说明不受支持的 Windows 版本不再能够与对 HTTPS 连接实施严格要求的外部服务进行交互。

愉快的部署!

八达通部署赞助商。NET Foundation - Octopus 部署

原文:https://octopus.com/blog/dotnet-foundation

Octopus Deploy sponsors the .NET Foundation

我很高兴地宣布,Octopus Deploy 现在是的企业赞助商。NET Foundation ,这是一个非营利性组织,旨在支持围绕。NET 平台。

作为一个独立的软件供应商,我们使用许多开源项目来构建 Octopus Deploy。我们在力所能及的地方直接向其中一些捐款,我们是 Material UICakeJUnitconservate以及其他一些公司的赞助商。我们还开源了很多自己的代码,包括我们的通信栈部署逻辑ORM ,我们所有的构建服务器集成,以及很快我们的触手部署代理。我们的团队成员还在工作和私人时间为各种开源项目做出贡献。健康的开源对我们的使命至关重要,也是我们的核心。

更一般地说,我们还依赖于围绕. NET 的充满活力的、可持续的开源生态系统。我们忘记了,但正是开源项目实现了单元测试、构建自动化、依赖注入和对象/关系映射等实践。网。并不是所有的开源项目都需要直接赞助,但是许多项目都可以从。NET Foundation 在倡导采用方面所做的努力以及对。NET 开源项目。

作为企业赞助商,我们在顾问委员会获得一个席位。这些座位很少,所以我们想明智地使用它。我们努力思考谁最适合服务于。NET 社区的长期利益,所以我们询问了最受欢迎的两个开源软件 Serilog 和 Autofac 的创始人 Nicholas Blumhardt。NET 项目-请坐。我有幸在过去的十年里认识了尼克,并在几年前与他共事,我真的认为尼克在顾问委员会的存在将会产生非常积极的影响。

我们认为。NET Foundation 如果不完全依赖微软提供资金,它可以产生更大的影响,而且随着越来越多的企业赞助商的出现,它将能够实现一些重要的事情,并成为一股有益的力量。作为一家为开源做出贡献并从中受益的公司,我们很自豪能够参与并回馈社区,并希望让开源变得更加可持续。

在 AWS CloudFormation 模板中使用 DSC 安装 Tentacles-Octopus Deploy

原文:https://octopus.com/blog/dsc-with-aws-cloudformation

Installing Tentacles with DSC in AWS CloudFormation templates

在之前的一篇博客文章中,我们查看了一些示例 CloudFormation 模板,它们在 VPC 中创建了一个新的 EC2 虚拟机。

这些模板在实例用户数据中有占位符脚本。在这篇博文中,我们将通过 Octopus DSC 模块,看看如何配置这些用户数据脚本来安装一个触手,作为目标或工作者。

安装目标

下面是完整的 CloudFormation 模板,我们用它在 VPC 中构建一个 EC2 实例,并将一个触手配置为目标:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  InstanceTypeParameter:
    Type: String
    Default: t3a.medium
    Description: Enter instance size. Default is t3a.medium.
  WorkstationIp:
    Type: String
    Description: The IP address of the workstation that can RDP into the instance.
  AMI:
    Type: String
    Default: ami-05bb2dae0b1de90b3
    Description: The Windows AMI to use.
  Key:
    Type: String
    Description: The key used to access the instance.
  APIKey:
    Type: String
    Description: The API key used to connect to the Octopus server.
  OctopusURL:
    Type: String
    Description: The Octopus server URL.   
  TentacleRole:
    Type: String
    Description: The tentacle role.   
  TentacleEnvironment:
    Type: String
    Description: The tentacle environment. 
  TentacleSpace:
    Type: String
    Description: The tentacle space.  
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: Windows Target VPC
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  SubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-east-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  InternetRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable
  SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "Octopus Target Group"
      GroupDescription: "Tentacle traffic in from hosted static ips, and RDP in from a personal workstation"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.156/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.42/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.31.180/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.132/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.94/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.173/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.171/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.7/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.147/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.240/32
        - IpProtocol: tcp
          FromPort: '3389'
          ToPort: '3389'
          CidrIp:  !Sub ${WorkstationIp}/32
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  Windows:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref AMI
      InstanceType:
        Ref: InstanceTypeParameter
      KeyName: !Ref Key
      SubnetId: !Ref SubnetA
      SecurityGroupIds:
        - Ref: InstanceSecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 250
      UserData:
        Fn::Base64: !Sub |
          <powershell>
          Set-Content -Path c:\dsc.ps1 -Value @"
          Configuration SampleConfig
          {
              param (`$ApiKey, `$OctopusServerUrl, `$Environments, `$Roles, `$ListenPort, `$Space)

              Import-DscResource -Module OctopusDSC

              Node "localhost"
              {
                  cTentacleAgent OctopusTentacle
                  {
                      Ensure = "Present"
                      State = "Started"

                      # Tentacle instance name. Leave it as 'Tentacle' unless you have more
                      # than one instance
                      Name = "Tentacle"

                      # Defaults to <MachineName>_<InstanceName> unless overridden
                      DisplayName = "My Tentacle"

                      # Defaults to 10933 unless otherwise specified
                      ListenPort = `$ListenPort

                      # Required parameters. See full properties list below
                      ApiKey = `$ApiKey
                      OctopusServerUrl = `$OctopusServerUrl
                      Environments = `$Environments
                      Roles = `$Roles
                      Space = `$Space # This is for versions 2019.1 and above.  If null or not specified, it uses the space designated as Default
                  }
              }
          }

          SampleConfig -ApiKey "${APIKey}" -OctopusServerUrl "${OctopusURL}" -Environments @("${TentacleEnvironment}") -Roles @("${TentacleRole}") -ListenPort 10933 -Space "${TentacleSpace}"

          Start-DscConfiguration .\SampleConfig -Verbose -wait

          Test-DscConfiguration
          "@

          Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false
          Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted
          Install-Module -Name OctopusDSC -Force -Confirm:$false
          Import-Module -Name OctopusDSC

          # Need .NET 4.8 to work around a bug in 4.7
          Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
          choco install dotnetfx -y

          # Dot source the DSC config to install the tentacle
          . c:\dsc.ps1
          </powershell>
      Tags:
        -
          Key: Application
          Value:  Windows Server
        -
          Key: Domain
          Value: None
        -
          Key: Environment
          Value: Test
        -
          Key: LifeTime
          Value: Transient
        -
          Key: Name
          Value:  Windows Server Worker
        -
          Key: OS
          Value: Windows
        -
          Key: OwnerContact
          Value: "@matthewcasperson"
        -
          Key: Purpose
          Value: MattC Test Worker
        -
          Key: Source
          Value: CloudForation Script in Octopus Deploy
  ElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      InstanceId: !Ref Windows
Outputs:
  PublicIp:
    Value:
      Fn::GetAtt:
        - Windows
        - PublicIp
    Description: Server's PublicIp Address 

与前一篇博文中描述的模板相比,该模板有两处变化。

添加了其他参数:

 APIKey:
    Type: String
    Description: The API key used to connect to the Octopus server.
  OctopusURL:
    Type: String
    Description: The Octopus server URL.   
  TentacleRole:
    Type: String
    Description: The tentacle role.   
  TentacleEnvironment:
    Type: String
    Description: The tentacle environment. 
  TentacleSpace:
    Type: String
    Description: The tentacle space. 

这些参数定义用于连接 Octopus 的 API 键、Octopus 服务器 URL 以及新目标的角色、环境和空间。

为了配置目标,我们在用户数据部分展开了脚本。让我们来分析一下这个脚本。

我们首先将 DSC 配置保存到第二个 PowerShell 脚本中。我们需要这样做,因为 PowerShell 会在运行其他脚本命令之前解析任何Configuration块。Configuration模块中的命令Import-DscResource -Module OctopusDSC将会失败,因为 DSC 模块尚未安装。

此处使用的解决方法是将Configuration块保存到第二个文件,并在安装 DSC 模块后对其进行点源处理:

Set-Content -Path c:\dsc.ps1 -Value @"
    Configuration SampleConfig
    {
        param (`$ApiKey, `$OctopusServerUrl, `$Environments, `$Roles, `$ListenPort, `$Space)

        Import-DscResource -Module OctopusDSC

        Node "localhost"
        {
            cTentacleAgent OctopusTentacle
            {
                Ensure = "Present"
                State = "Started"

                # Tentacle instance name. Leave it as 'Tentacle' unless you have more
                # than one instance
                Name = "Tentacle"

                # Defaults to <MachineName>_<InstanceName> unless overridden
                DisplayName = "My Tentacle"

                # Defaults to 10933 unless otherwise specified
                ListenPort = `$ListenPort

                # Required parameters. See full properties list below
                ApiKey = `$ApiKey
                OctopusServerUrl = `$OctopusServerUrl
                Environments = `$Environments
                Roles = `$Roles
                Space = `$Space # This is for versions 2019.1 and above.  If null or not specified, it uses the space designated as Default
            }
        }
    }

    SampleConfig -ApiKey "${APIKey}" -OctopusServerUrl "${OctopusURL}" -Environments @("${TentacleEnvironment}") -Roles @("${TentacleRole}") -ListenPort 10933 -Space "${TentacleSpace}"

    Start-DscConfiguration .\SampleConfig -Verbose -wait

    Test-DscConfiguration
    "@ 

要安装 DSC 模块,我们需要安装 NuGet provider,然后从 PowerShell Gallery 安装 DSC 模块:

Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false
Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted
Install-Module -Name OctopusDSC -Force -Confirm:$false
Import-Module -Name OctopusDSC 

由于版本的错误。NET 默认安装在 VM 上,我们需要安装。净 4.8。这是通过巧克力完成的:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco install dotnetfx -y 

具备了所有先决条件后,我们开始进行 DSC 配置,现在将成功完成:

. c:\dsc.ps1 

验证安装

用户数据脚本的日志文件在C:\ProgramData\Amazon\EC2-Windows\Launch\Log\UserdataExecution.log中。如果我们在新创建的 EC2 实例中查看该文件的内容,我们将看到正在安装的触手:

安装一个工人

安装工人的模板非常相似:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  InstanceTypeParameter:
    Type: String
    Default: t3a.medium
    Description: Enter instance size. Default is t3a.medium.
  WorkstationIp:
    Type: String
    Description: The IP address of the workstation that can RDP into the instance.
  AMI:
    Type: String
    Default: ami-05bb2dae0b1de90b3
    Description: The Windows AMI to use.
  Key:
    Type: String
    Description: The key used to access the instance.
  APIKey:
    Type: String
    Description: The API key used to connect to the Octopus server.
  OctopusURL:
    Type: String
    Description: The Octopus server URL.   
  WorkerPools:
    Type: String
    Description: The worker pools to add the tentacle to. 
  TentacleSpace:
    Type: String
    Description: The tentacle space.  
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: Windows Target VPC
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  SubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-east-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  InternetRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable
  SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "Octopus Target Group"
      GroupDescription: "Tentacle traffic in from hosted static ips, and RDP in from a personal workstation"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.156/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.42/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.31.180/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.132/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.94/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.173/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.171/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.7/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.147/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.240/32
        - IpProtocol: tcp
          FromPort: '3389'
          ToPort: '3389'
          CidrIp:  !Sub ${WorkstationIp}/32
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  Windows:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref AMI
      InstanceType:
        Ref: InstanceTypeParameter
      KeyName: !Ref Key
      SubnetId: !Ref SubnetA
      SecurityGroupIds:
        - Ref: InstanceSecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 250
      UserData:
        Fn::Base64: !Sub |
          <powershell>
          Set-Content -Path c:\dsc.ps1 -Value @"
          Configuration SampleConfig
          {
              param (`$ApiKey, `$OctopusServerUrl, `$WorkerPools, `$ListenPort, `$Space)

              Import-DscResource -Module OctopusDSC

              Node "localhost"
              {
                  cTentacleAgent OctopusTentacle
                  {
                      Ensure = "Present"
                      State = "Started"

                      # Tentacle instance name. Leave it as 'Tentacle' unless you have more
                      # than one instance
                      Name = "Tentacle"

                      # Defaults to <MachineName>_<InstanceName> unless overridden
                      DisplayName = "My Tentacle"

                      # Defaults to 10933 unless otherwise specified
                      ListenPort = `$ListenPort

                      # Required parameters. See full properties list below
                      ApiKey = `$ApiKey
                      OctopusServerUrl = `$OctopusServerUrl
                      WorkerPools = `$WorkerPools
                      Space = `$Space # This is for versions 2019.1 and above.  If null or not specified, it uses the space designated as Default
                  }
              }
          }

          SampleConfig -ApiKey "${APIKey}" -OctopusServerUrl "${OctopusURL}" -WorkerPools @("${WorkerPools}") -ListenPort 10933 -Space "${TentacleSpace}"

          Start-DscConfiguration .\SampleConfig -Verbose -wait

          Test-DscConfiguration
          "@

          Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false
          Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted
          Install-Module -Name OctopusDSC -Force -Confirm:$false
          Import-Module -Name OctopusDSC

          # Need .NET 4.8 to work around a bug in 4.7
          Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
          choco install dotnetfx -y

          # Dot source the DSC config to install the tentacle
          . c:\dsc.ps1
          </powershell>
      Tags:
        -
          Key: Application
          Value:  Windows Server
        -
          Key: Domain
          Value: None
        -
          Key: Environment
          Value: Test
        -
          Key: LifeTime
          Value: Transient
        -
          Key: Name
          Value:  Windows Server Worker
        -
          Key: OS
          Value: Windows
        -
          Key: OwnerContact
          Value: "@matthewcasperson"
        -
          Key: Purpose
          Value: MattC Test Worker
        -
          Key: Source
          Value: CloudFormation Script in Octopus Deploy
  ElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      InstanceId: !Ref Windows
Outputs:
  PublicIp:
    Value:
      Fn::GetAtt:
        - Windows
        - PublicIp
    Description: Server's PublicIp Address 

要安装工作线程,我们需要删除角色和环境,并定义一个工作线程池参数:

 WorkerPools:
    Type: String
    Description: The worker pools to add the tentacle to. 

然后,我们从 DSC 配置中删除角色和环境,并定义工作池:

# Required parameters. See full properties list below
ApiKey = `$ApiKey
OctopusServerUrl = `$OctopusServerUrl
WorkerPools = `$WorkerPools
Space = `$Space # This is for versions 2019.1 and above.  If null or not specified, it uses the space designated as Default 

结论

通过利用用户数据脚本和 Octopus DSC 模块,我们可以快速启动新的虚拟机,在 Octopus 中自动安装和注册目标或工人。在 PowerShell 试图解析 DSC 配置之前,安装 DSC 模块需要做一些工作,但是一旦您理解了 DSC 的特点和解决方法,这个过程就相对容易实现了。

使用动态数量的参数从 PowerShell 调用可执行文件——Octopus Deploy

原文:https://octopus.com/blog/dynamic-argument-list-when-calling-executable-from-powershell

从 PowerShell 调用可执行文件很容易——大多数时候,您只需在前面加上一个&。为了说明,让我们看看这个 C#可执行文件:

static void Main(string[] args)
{
    for (int i = 0; i < args.Length; i++)
    {
        Console.WriteLine("[" + i + "] = '" + args[i] + "'");
    }
} 

如果我们这样称呼它:

& .\Argsy.exe arg1 "argument 2" 

我们得到:

[0] = 'arg1'
[1] = 'argument 2' 

PowerShell 变量也可以传递给参数:

$myvariable = "argument 2"
& .\Argsy.exe arg1 $myvariable

# Output:
[0] = 'arg1'
[1] = 'argument 2' 

注意,$myvariable的值包含一个空格,但是 PowerShell 很聪明,将整个值作为一个参数传递。

当您想要有条件地或动态地添加参数时,这就变得棘手了。例如,您可能会尝试这样做:

$args = ""
$environments = @("My Environment", "Production")
foreach ($environment in $environments) 
{
    $args += "--environment "
    $args += $environment + " "
}

& .\Argsy.exe $args 

然而,您会对输出感到失望:

[0] = '--environment My Environment --environment Production ' 

正确的方式

相反,这样做的方法是创建一个数组。您仍然可以在 PowerShell 中使用+=语法来构建阵列:

$args = @() # Empty array
$environments = @("My Environment", "Production")
foreach ($environment in $environments) 
{
    $args += "--environment"
    $args += $environment
}
& .\Argsy.exe $args 

它输出了我们期望的结果:

[0] = '--environment'
[1] = 'My Environment'
[2] = '--environment'
[3] = 'Production' 

您也可以将常规字符串与数组混合使用:

& .\Argsy.exe arg1 "argument 2" $args

# Output:
[0] = 'arg1'
[1] = 'argument 2'
[2] = '--environment'
[3] = 'MyEnvironment'
[4] = '--environment'
[5] = 'Production' 

边缘情况

对于我上面所说的传递一个带有所有参数的字符串,有一种非常奇怪的情况。以这个例子为例,它与上面的例子相似:

$args = "--project Foo --environment My Environment --environment Production"
& .\Argsy.exe $args

# Output: 
[0] = '--project Foo --environment My Environment --environment Production' 

要使它按预期工作,只需在第一个参数的两边加上引号,行为就会完全改变!(反勾号是 PowerShell 的转义字符)

$args = "`"--project`" Foo --environment My Environment --environment Production"
& .\Argsy.exe $args

# Output: 
[0] = '--project'
[1] = 'Foo'
[2] = '--environment'
[3] = 'My'
[4] = 'Environment'
[5] = '--environment'
[6] = 'Production' 

如果不引用第一个参数,行为不会改变:

$args = "--project `"Foo`" --environment My Environment --environment Production"
& .\Argsy.exe $args

# Output: 
[0] = '--project Foo --environment My Environment --environment Production' 

啊,PowerShell。总是充满惊喜!

部署到动态供应的基础设施- Octopus 部署

原文:https://octopus.com/blog/dynamic-infrastructure-during-deployment

Deploying to dynamically provisioned infrastructure

使用项目触发器,可以将您的应用程序部署到动态创建的部署目标。当您有一个配置了伸缩功能的应用程序时,这种方法特别有效。随着更多的服务器被添加来处理负载,您的应用程序将被自动部署。也就是说,在有些情况下,创建部署目标是部署过程的一部分,这可能会有点棘手,因为 Octopus Deploy 会在部署开始时选择要部署到的目标。在本文中,我将向您展示如何将动态添加的目标作为部署过程的一部分。

示例场景

假设我们有一个在 Azure 上使用 Kubernetes 的应用程序。我们使用 Azure Resource Manager (ARM)模板来创建我们的 Kubernetes 集群,然后在其上部署我们的应用程序。一旦不再需要群集,我们就将其拆除以节省成本。当我们需要的云资源类型很昂贵,并且我们只在有限的时间内需要它时,这种方法很有用。我们的流程如下所示:

我们动态地创建一个资源组,一个 Kubernetes 集群,然后开始向它部署。

为了添加 Kubernetes 集群,我们首先需要向创建 K8 集群步骤添加一个部署后脚本。编辑步骤并点击配置功能按钮:

选择自定义部署脚本:

在 K8s 的例子中,Octopus Deploy 创建了一个名为New-Octopus kubernetarget的助手 cmdlet。对于本文,我使用一个可以添加任何类型目标的脚本。

展开自定义部署脚本部分,并在后期部署脚本窗口中输入以下内容。该脚本将首先检查它是否已经存在,如果不存在,则为 API 调用创建有效负载以添加它:

# Get current clustername
$kubernetesCluster = (Invoke-RestMethod -Method Get -Uri "$($OctopusParameters['Octopus.Web.BaseUrl'])/api/Spaces-1/machines/all" -Headers @{"X-Octopus-ApiKey"="$($OctopusParameters['Global.Octopus.ApiKey'])"}) | Where-Object {$_.Name -eq $OctopusParameters['Project.Azure.Kubernetes.ClusterName']}

# Check for null
if ($null -eq $kubernetesCluster)
{
  # Create payload for call
  $kubernetesClusterTarget = @{
  #Id= $null
  MachinePolicyId= "MachinePolicies-1"
  Name= $OctopusParameters['Project.Azure.Kubernetes.ClusterName']
  IsDisabled= $false
  HealthStatus= "Unknown"
  HasLatestCalamari= $true
  StatusSummary= $null
  IsInProcess= $true
  EndPoint= @{
  #Id= $null
  CommunicationStyle= "Kubernetes"
  Links= $null
  Authentication= @{
  AuthenticationType= "KubernetesAzure"
  AccountId= $OctopusParameters['Project.Azure.Account']
  ClusterName= $OctopusParameters['Project.Azure.Kubernetes.ClusterName']
  ClusterResourceGroup= $OctopusParameters['Project.Azure.ResourceGroup.Name']
  }
  AccountType= "AzureServicePrincipal"
  ClusterUrl= $null
  ClusterCertificate= $null
  SkipTlsVerification= $null
  DefaultWorkerPoolId= "WorkerPools-41"
  }
  Links= $null
  TenantedDeploymentParticipation= "Untenanted"
  Roles= @("OctoPetShop-K8")
  EnvironmentIds= @($OctopusParameters['Octopus.Environment.Id'])
  TenantIds= @()
  TenantTags= @()
  }

  # Convert to json
  $jsonBody = $kubernetesClusterTarget | ConvertTo-Json -Depth 10

  # Add cluster to deployment targets
  Invoke-RestMethod -Method Post -Uri "$($OctopusParameters['Octopus.Web.BaseUrl'])/api/Spaces-1/machines" -Body $jsonBody -Headers @{"X-Octopus-ApiKey"="$($OctopusParameters['Global.Octopus.ApiKey'])"}
} 

然后,我们更新项目设置以允许创建部署,即使目标不存在:

现在我们安排我们的部署,看着它进行!

为什么跳过了一些步骤?

尽管成功地创建了 Kubernetes 集群并将其注册到 Octopus Deploy,但是部署的结果看起来像是跳过了部署到 Kubernetes 集群的所有步骤。部署目标是在部署开始时选择的,由于集群还没有注册到 Octopus Deploy,Octopus 确定没有可用的部署目标,因此跳过了所有的部署步骤。

包括新的目标

将健康检查步骤添加到您的流程中,并将其配置为包含新的目标,可以解决这个问题。

配置运行状况检查时,选择connection-only test作为运行状况检查类型,并在新部署目标部分选择Include new deployment targets

【T2

完成此操作后,新配置的 Kubernetes 集群将包含在部署中,我们的其余步骤将成功部署!

摘要

虽然这不是一个常见的场景,但是这篇文章演示了如何在您的部署中包含在部署过程中创建的目标。

管理动态目标- Octopus 部署

原文:https://octopus.com/blog/dynamic-infrastructure

Dynamic targets

在这篇文章中,我们将通过一个动态创建和拆除 Azure 基础设施的例子,为每个测试人员创建按需 Web 应用。我们还将了解如何在多个地理区域部署网站,以及如何拆除这些网站。这也将是在 PaaS 部署目标和我们的发布视频中讨论的一些主题的技术概述。

在 2018.5 中,我们引入了从您的部署流程中轻松管理您的 Azure 部署目标的能力。以前在 Octopus 中,你可以使用 Azure PowerShell 模块,你可以在你的 Azure 订阅中创建资源组和 Web 应用程序,但如果不做一些繁重的工作,你就无法向它们部署应用程序。新的动态目标 cmdlets 使这变得简单明了。

显然,您将需要部署一个应用程序,但是我将把它作为读者的练习。

设置

首先,我们需要配置 Octopus 来管理我们的新项目。

创建 Azure 帐户

参见关于创建 Azure 服务主体帐户的文档以获取说明。

创建环境并配置动态基础架构

创建一个新的环境,如果你还没有的话。默认情况下,环境不允许创建或删除动态目标,因此您需要通过编辑环境设置来启用它。

Environment configuration

创造新的生命周期

为了简化我们的 QA 部署,并防止它部署到其他环境(如生产),我们可以创建一个新的生命周期,只允许部署到我们的新环境。

QA Only Lifecycle

创建脚本模块

脚本模块使您能够创建可以跨项目共享的函数。要生成唯一的站点名称,请将以下 PowerShell 函数放入脚本模块中,该模块位于部分下:

function GetSiteName($prefix)
{
    # Octopus variables
    $environment = $OctopusParameters['Octopus.Environment.Name'].Replace(" ", "").Replace(".", "-")
    $tenant = $OctopusParameters['Octopus.Deployment.Tenant.Name'].Replace(".", "-")

    # A unique name based on the Octopus environment, release, and tenant
    $uniqueName = "$prefix-$environment-$tenant"

    return $uniqueName
} 

创建变量集

因为我们在安装和拆卸项目之间需要一个公共变量值,所以我们可以把它放在一个变量集中,也可以在部分中找到。

添加一个新的变量集,并创建一个变量:

Variable Set

创建安装项目

我们需要创建的第一个项目是创建所有基础设施和部署应用程序的项目。

创建一个新项目并进行一些初始设置:

  • 进程下,将生命周期默认更改为新的生命周期,并包含新的脚本模块
  • 变量->-库集下,点击包含库变量集,选择上一步创建的变量集
  • 设置下:
    • 部署目标更改为允许在没有部署目标时创建部署
    • 跳过部署目标更改为如果部署目标不可用或变得不可用。如果不更改此设置,任何已从 Azure 中删除但未在 Octopus 中清理的 Web 应用目标都将导致部署失败。这是可选的,取决于您的要求,更多信息请参见文档

在为这个项目设置流程时,我们将需要一个 Azure 帐户,有几种不同的方式为步骤提供帐户:

  1. 直接上台阶。

  2. 通过租户变量,如果您的租户有不同的 Azure 帐户。

    • 转到变量 - > 项目模板点击添加模板,将控件类型设置为 Azure Account 并给变量起个名字。稍后当我们设置租户时,这个变量将被赋予一个值。
  3. 通过项目变量。

    • 进入变量页面,新建一个变量,将其类型设置为 Azure Account ,然后选择之前创建的 Azure 账号。

Account Project Variable

Account Variable Value Selector

部署流程

让我们配置一个部署流程。

Deployment process

这里的第一步是一个 Azure PowerShell 脚本。该步骤应该配置为在 Octopus 服务器上运行,此时它不需要角色。

该脚本将查询 Azure 以检查目标资源组是否存在,并在需要时创建资源组、应用服务计划和 Web 应用。我们还在资源组上使用一个标签设置了一个截止日期,这将在以后的拆卸项目中使用。

脚本中的最后一行是让我们下一步工作的魔法,它将在 Octopus 服务器中创建一个新的 Azure Web 应用目标,并为其分配一个角色 QATest-updateIfExisting参数将允许命令创建或更新一个同名的现有目标。

$uniqueName = GetSiteName($Prefix)
Set-OctopusVariable -name "SiteName" -value $uniqueName
Set-OctopusVariable -name "Url" -value "https://$uniqueName.azurewebsites.net/"

# Check for resource group
Get-AzureRmResourceGroup -Name $uniqueName -ErrorVariable notPresent -ErrorAction SilentlyContinue;

if ($notPresent) {
  # Create resources in Azure

  # set expiry tag on resource group to be used by our teardown script
  # this could be calculated to be a the end of the week or a specific future date
  $expiry =  ([System.DateTime]::Today.AddDays(7)).ToShortDateString();

  New-AzureRmResourceGroup -Name $uniqueName -Location "WestUS" -Tag @{Expiry="$expiry"}
  New-AzureRmAppServicePlan -Name $uniqueName -Location "WestUS" -ResourceGroupName $uniqueName -Tier Free
  New-AzureRmWebApp -Name $uniqueName -Location "WestUS" -AppServicePlan $uniqueName -ResourceGroupName $uniqueName

  # Create new target in Octopus
  Set-OctopusVariable -name "Action" -value "NewSite"
}
else {
  Set-OctopusVariable -name "Action" -value "ExistingSite"
}

# create a new Octopus Azure Web App Target
New-OctopusAzureWebAppTarget -Name $uniqueName `
                             -AzureWebApp $uniqueName `
                             -AzureResourceGroupName $uniqueName `
                             -OctopusAccountIdOrName $OctopusParameters["Azure Account"] `
                             -OctopusRoles "QATest" `
                             -updateIfExisting 

下一步是部署 Azure Web 应用步骤。这是我们将应用程序部署到我们在上一步中创建的目标的地方。您需要将目标角色设置为 QATest ,它不会出现在列表中,您需要键入它并选择 Add

未来将会改进对角色的管理,但是现在,你需要手动输入角色名称

最后一步是通知步骤,可以是 Slack、电子邮件或其他内容。

在我的时差通知步骤中,我设置了以下自定义设置:

标题Deployment to #{Octopus.Deployment.Tenant.Name}

消息#{Octopus.Project.Name} release #{Octopus.Release.Number} to #{Octopus.Environment.Name} for #{Octopus.Deployment.Tenant.Name} Deployed #{Octopus.Action[Setup Azure Web App].Output.Action} to #{Octopus.Action[Setup Azure Web App].Output.Url}

在第一个脚本步骤中创建了UrlAction输出参数。

创建租户

在这个例子中,我利用租户来演示如何构建 QA 环境。一个租户可能代表一个测试人员或者一个客户。通过应用这种模式,您可以将您的部署从几个测试人员扩展到数百个测试人员,为每个测试人员部署 Azure 基础设施和最新的应用程序。

租户菜单下,添加两个新租户,并将它们连接到 Web App Setup 项目,以及我们之前创建的环境。

Tenants

对于每个租户,您需要单击连接项目并选择我们刚刚创建的安装项目。如果您已经选择将您的 Azure 帐户变量创建为一个项目模板变量,您将需要在这一步提供实际的 Azure 帐户。这允许您在需要时为每个租户提供不同的帐户。

租户用于您的部署将允许您使用租户变量为每个租户的每个部署提供配置。例如,每个测试人员/客户可能有他们自己的数据库,数据库名称由每个租户的变量提供,以在 web app 部署项目中构建数据库连接字符串。有关更多信息和示例,请参见我们的文档

拆卸

所有这些 Azure 资源都可能让你花钱,即使没有人使用它们,所以我们可以使用第二个项目来拆除 Azure 和 Octopus 的应用程序。

创建一个新项目,并在设置中进行一些设置配置:

  • 部署目标更改为允许在没有部署目标时创建部署。
  • 跳过部署目标更改为如果部署目标不可用或变得不可用

变量->-下,库集合包括我们之前创建的库变量集合。这将在我们的拆卸脚本中使用。

使用另一个新的 Octopus infra structure cmdletRemove-OctopusTarget,我们可以在单个 Azure PowerShell 脚本步骤中拆除 Octopus 目标和 Azure 资源。

该脚本使用 Azure 资源组上的到期标签来确定要删除哪些资源。

$date = [System.DateTime]::Today
# find all resource groups marked for expiry, with a name starting with #{Prefix}
Write-Host "Checking for resources expiring on or before $date"
$resourceGroups = Get-AzureRmResourceGroup | `
                        Where { $_.ResourceGroupName.StartsWith($Prefix) `
                                -and $_.Tags -ne $null `
                                -and $_.Tags.ContainsKey("Expiry") `
                                -and [DateTime]::Parse($_.Tags["Expiry"]) -ile $date }

Write-Host "Found $($resourceGroups.Count) resource groups"

foreach ($rg in $resourceGroups) {
  Write-Host "Removing $($rg.ResourceGroupName)"
  Remove-AzureRmResourceGroup -Name $rg.ResourceGroupName -Force
  Remove-OctopusTarget -targetIdOrName $rg.ResourceGroupName
} 

在运行拆除项目后,所有带有执行日期或更早到期标记的资源组将被移除,相应的 Octopus Azure Web App 目标也将被移除。

使用最近推出的计划项目触发器你可以触发每晚或每周执行的拆卸脚本。

Azure 资源管理器模板和云区域

即使在2018.5中添加了所有新的目标,云区域仍然在确定您的部署脚本的范围中发挥作用,以支持多区域部署模式。例如,您可以整合云区域来运行不同地理区域的 PowerShell 脚本或 Azure 资源管理器模板。

设置

对于这个例子,我们将创建两个云区域

Cloud Regions

然后,我们可以使用这些云区域来创建跨越两个不同 Azure 地理区域的基础设施,并为每个区域部署一个 ARM 模板

现在,让我们创建一个新项目来运行我们的 Azure 资源管理器模板:

ARM deployment process

第一步是部署 Azure 资源组:

ARM Step

将步骤设置为根据我们分配给新的云区域的角色运行,该云区域是在上一步骤中创建的。

在帐户部分,您可以直接选择一个帐户,也可以将其绑定到 Azure 帐户变量。

ARM 步骤还要求目标资源组存在于 Azure 中,然后才能部署模板在资源组中创建资源。以前,这必须在更早的步骤中完成,在 2018.5 中,我们允许在该步骤中运行配置脚本。这些可以通过该步骤顶部的配置功能选项打开。一旦打开,您就可以添加预部署和后部署 PowerShell 部署脚本。

因为我们希望每个地理区域的资源组有不同的名称和位置,所以我们可以使用变量来提供区域名称和资源组名称,这样它们就可以不同。在资源组名称字段中,添加变量语法#{SiteResourceGroup},并在预部署脚本部分添加以下 PowerShell 命令:

New-AzureRmResourceGroup -Name $SiteResourceGroup -Location $SiteLocation -Force 

对于 ARM 模板本身来说,最简单的入门方式就是去 Azure 门户,在你的模板中创建你想要的所有资源。对于这个例子,我创建了一个资源组应用服务应用服务计划。然后,在资源组自动化脚本部分,您可以获取重新创建资源所需的模板。或者你可以抓一个样品

您还需要对模板进行一处修改,以允许区域作为输入参数,将以下 JSON 添加到 parameters 部分,不要忘记前一个参数后面的逗号:

"location": {
    "defaultValue": "Australia Southeast",
    "type": "String"
} 

然后将模板中所有出现的location值替换为"location": "[parameters('location')]"

当您将最终模板放入步骤的模板源中时,它将提取参数并允许您用自己的值替换这些值。

ARM Parameters

ARM 模板流程的最后一部分是添加一个后期部署脚本,以在 Octopus 中创建新的 Azure Web App 目标:

New-OctopusAzureWebAppTarget -name $SiteName `
                             -azureWebApp $SiteName `
                             -azureResourceGroupName $SiteResourceGroup  `
                             -octopusAccountIdOrName "azure" `
                             -octopusRoles "CloudWebSite" `
                             -updateIfExisting 

现在,创建支持我们的流程所需的所有变量,注意不同云区域目标的一些值的范围:

Arm template project variables

最后,我们需要在流程中添加一个部署 Azure Web App 步骤,该步骤将针对CloudWebSite角色运行。这与我们在上面第一个例子中使用的过程完全相同。

拆卸

您可以使用与前面的拆卸示例相同的脚本,通过在 ARM 模板步骤的预部署脚本中向New-AzureRmResourceGroup命令添加一个Expiry标记,或者您可以将一个空的 ARM 模板部署到同一个资源组:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {},
    "resources": []
} 

自动部署

随着新的 Azure 目标的引入,您还可以利用自动部署触发器,允许您在应用程序流程的单独部署流程中拥有基础架构脚本/模板。自动部署触发器可以设置为在创建 web 应用程序的新实例时触发部署。

结论

新的动态目标供应 cmdlets 填补了我们在版本 3 中首次实现 Azure Web App 目标的空白。现在,您可以对您的目标进行全面的端到端管理。对于动态目标来说,这也不是路的尽头,随着动态环境和维护任务等功能的出现,它们将开始发挥更大的作用。我们甚至可以添加一个用户界面,这样你就可以少写一行代码。

愉快的部署。

用 Terraform 和 AWS 自动伸缩组构建动态工人大军- Octopus Deploy

原文:https://octopus.com/blog/dynamic-worker-army

Building a dynamic worker army with Terraform and AWS autoscaling groups

基础设施即代码(IaC)的出现是一个巨大的飞跃,尤其是在云领域。以编程方式定义基础设施外观的能力带来了环境一致性和更可预测的应用程序行为。

云提供商已经将基础设施作为代码,提供定制的 IaC 实现来供应和配置他们产品上的资源。不幸的是,这意味着您必须学习多种工具来与不同的提供商合作;例如,亚马逊网络服务(AWS) CloudFormation 或微软 Azure 资源管理器(ARM)模板。HashiCorp 创建了 Terraform 来解决这个问题,这是一个与多个提供商合作的单一工具。

在本文中,我将向您展示如何使用 Terraform 和 AWS autoscaling 为 Octopus Deploy 动态创建工人。

Octopus 中的 Workers 使您能够将部署工作转移到池中运行的其他机器上。这可以大大减少在 Octopus 服务器上执行的工作,使其能够更容易地执行更多的部署。工人池可以通过启用可由多个项目和团队使用的专用机器池来提高可伸缩性。这方面的例子包括利用员工进行数据库部署,部署到云基础设施,如 Azure、AWS 和 Kubernetes (K8s)等。

什么是 AWS 自动缩放?

AWS 自动缩放是基于指定标准增加或减少资源的能力。自动缩放的一个常见用例是电子商务网站。随着需求的增加,需要创建更多的服务器来处理负载。当负载减少时,可以自动取消供应额外的资源。这使得电子商务保持最少数量的服务器运行,并保持主机成本下降。

在这篇文章中,我们使用 AWS autoscaling 来动态添加更多的工人,以使团队尽可能高效地执行他们的部署。

使用 Terraform 调配我们的云基础架构

使用声明性配置文件,Terraform 可以创建新资源,管理现有资源,或者销毁不再需要的资源。

有了 Terraform,我们能够在一个文件中定义我们所有的资源,或者在逻辑上将它们分开。在这篇文章中,我们将把我们的文件分开,以便于使用。

我们将使用八个文件:

  • 自动缩放. tf
  • autoscalingpolicy.tf
  • 后端. tf
  • installTentacle.sh
  • provider.tf
  • 安全组. tf
  • vars.tf
  • vpc.tf

自动缩放. tf

该文件包含用于创建 AWS 自动缩放组的资源声明和用于我们的工人的启动配置。我创建了两个启动配置,一个用于 Windows,一个用于 Linux。每个启动配置执行一个脚本,自动安装和配置 Octopus Deploy 触手软件,然后将它作为一个工作器连接到我们的 Octopus Deploy 服务器。为了说明脚本如何连接到启动配置,Linux 启动配置读取一个包含命令的文件(installTentacle.sh ),而 Windows 启动配置内联了脚本。对于 Windows 和 Linux,我们正在配置轮询触角。

 resource "aws_launch_configuration" "dynamic-linux-worker-launchconfig" {
    name_prefix = "dynamic-linux-worker-launchconfig"
    image_id = "${var.LINUX_AMIS}"
    instance_type = "t2.micro"

    security_groups = ["${aws_security_group.allow-octopusserver.id}"]

    # script to run when created
user_data = "${file("installTentacle.sh")}"

}

resource "aws_launch_configuration" "dynamic-windows-worker-launchconfig" {
    name_prefix = "dynamic-windows-worker-launchconfig"
    image_id = "${var.WINDOWS_AMIS}"
    instance_type = "t2.micro"

    security_groups = ["${aws_security_group.allow-octopusserver.id}"]

    user_data = <<-EOT
<powershell>
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

$OctopusName = (Invoke-RestMethod http://169.254.169.254/latest/meta-data/hostname)

choco install octopusdeploy.tentacle -y

& "C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" create-instance --config "c:\octopus\home"

& "C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" new-certificate --if-blank

& "C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" configure --noListen True --reset-trust --app "c:\octopus\applications"

Write-Host "Running register with..."

& "C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" register-worker --server "#{Project.Octopus.Server.Url}" --apiKey "#{Project.Octopus.Server.ApiKey}"  --comms-style "TentacleActive" --server-comms-port "#{Project.Octopus.Server.PollingPort}" --workerPool "#{Project.Octopus.Server.WorkerPool}" --policy "#{Project.Octopus.Server.MachinePolicy}" --space "#{Project.Octopus.Server.Space}" --name $OctopusName

Write-Host "Finished register with..."

& "C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" service --install

& "C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" service --start

</powershell>

  EOT
}

resource "aws_autoscaling_group" "dynamic-linux-worker-autoscaling" {
    name = "dynamic-linux-worker-autoscaling"
    vpc_zone_identifier = ["${aws_subnet.worker-public-1.id}", "${aws_subnet.worker-public-2.id}", "${aws_subnet.worker-public-3.id}"]
    launch_configuration = "${aws_launch_configuration.dynamic-linux-worker-launchconfig.name}"
    min_size = 2
    max_size = 3
    health_check_grace_period = 300
    health_check_type = "EC2"
    force_delete = true

    tag {
        key = "Name"
        value = "Octopus Deploy Linux Worker"
        propagate_at_launch = true
    }
}

resource "aws_autoscaling_group" "dynamic-windows-worker-autoscaling" {
    name = "dynamic-windows-worker-autoscaling"
    vpc_zone_identifier = ["${aws_subnet.worker-public-1.id}", "${aws_subnet.worker-public-2.id}", "${aws_subnet.worker-public-3.id}"]
    launch_configuration = "${aws_launch_configuration.dynamic-windows-worker-launchconfig.name}"
    min_size = 2
    max_size = 3
    health_check_grace_period = 300
    health_check_type = "EC2"
    force_delete = true

    tag {
        key = "Name"
        value = "Octopus Deploy Windows Worker"
        propagate_at_launch = true
    }
} 

autoscalingpolicy.tf

该文件包含策略和触发器定义,用于扩大和缩小我们的 EC2 实例:

# scale up alarm

resource "aws_autoscaling_policy" "linux-worker-cpu-policy" {
  name                   = "linux-worker-cpu-policy"
  autoscaling_group_name = "${aws_autoscaling_group.dynamic-linux-worker-autoscaling.name}"
  adjustment_type        = "ChangeInCapacity"
  scaling_adjustment     = "1"
  cooldown               = "300"
  policy_type            = "SimpleScaling"
}

resource "aws_cloudwatch_metric_alarm" "linux-worker-cpu-alarm" {
  alarm_name          = "linux-worker-cpu-alarm"
  alarm_description   = "linux-worker-cpu-alarm"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "30"

  dimensions = {
    "AutoScalingGroupName" = "${aws_autoscaling_group.dynamic-linux-worker-autoscaling.name}"
  }

  actions_enabled = true
  alarm_actions   = ["${aws_autoscaling_policy.linux-worker-cpu-policy.arn}"]
}

# scale down alarm
resource "aws_autoscaling_policy" "linux-worker-cpu-policy-scaledown" {
  name                   = "linux-worker-cpu-policy-scaledown"
  autoscaling_group_name = "${aws_autoscaling_group.dynamic-linux-worker-autoscaling.name}"
  adjustment_type        = "ChangeInCapacity"
  scaling_adjustment     = "-1"
  cooldown               = "300"
  policy_type            = "SimpleScaling"
}

resource "aws_cloudwatch_metric_alarm" "linux-worker-cpu-alarm-scaledown" {
  alarm_name          = "linux-worker-cpu-alarm-scaledown"
  alarm_description   = "linux-worker-cpu-alarm-scaledown"
  comparison_operator = "LessThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "5"

  dimensions = {
    "AutoScalingGroupName" = "${aws_autoscaling_group.dynamic-linux-worker-autoscaling.name}"
  }

  actions_enabled = true
  alarm_actions   = ["${aws_autoscaling_policy.linux-worker-cpu-policy-scaledown.arn}"]
}

resource "aws_autoscaling_policy" "windows-worker-cpu-policy" {
  name                   = "windows-worker-cpu-policy"
  autoscaling_group_name = "${aws_autoscaling_group.dynamic-windows-worker-autoscaling.name}"
  adjustment_type        = "ChangeInCapacity"
  scaling_adjustment     = "1"
  cooldown               = "300"
  policy_type            = "SimpleScaling"
}

resource "aws_cloudwatch_metric_alarm" "windows-worker-cpu-alarm" {
  alarm_name          = "windows-worker-cpu-alarm"
  alarm_description   = "windows-worker-cpu-alarm"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "30"

  dimensions = {
    "AutoScalingGroupName" = "${aws_autoscaling_group.dynamic-windows-worker-autoscaling.name}"
  }

  actions_enabled = true
  alarm_actions   = ["${aws_autoscaling_policy.windows-worker-cpu-policy.arn}"]
}

# scale down alarm
resource "aws_autoscaling_policy" "windows-worker-cpu-policy-scaledown" {
  name                   = "windows-worker-cpu-policy-scaledown"
  autoscaling_group_name = "${aws_autoscaling_group.dynamic-windows-worker-autoscaling.name}"
  adjustment_type        = "ChangeInCapacity"
  scaling_adjustment     = "-1"
  cooldown               = "300"
  policy_type            = "SimpleScaling"
}

resource "aws_cloudwatch_metric_alarm" "windows-worker-cpu-alarm-scaledown" {
  alarm_name          = "windows-worker-cpu-alarm-scaledown"
  alarm_description   = "windows-worker-cpu-alarm-scaledown"
  comparison_operator = "LessThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "5"

  dimensions = {
    "AutoScalingGroupName" = "${aws_autoscaling_group.dynamic-windows-worker-autoscaling.name}"
  }

  actions_enabled = true
  alarm_actions   = ["${aws_autoscaling_policy.windows-worker-cpu-policy-scaledown.arn}"]
} 

后端. tf

从本地机器执行 Terraform 时,状态文件存储在本地。Octopus Deploy 不会在执行应用后保留状态文件,因此有必要将我们的状态文件存储在外部存储上。该文件告诉 Terraform 使用 AWS S3 存储桶来存储状态文件:

terraform {
    backend "s3" {
        bucket = "#{Project.AWS.S3.Bucket}"
        key = "#{Project.AWS.S3.Key}"
        region = "#{Project.AWS.Region}"
    }
} 

installTentacle.sh

这是 Linux 变体的 autoscaling.tf 启动配置中引用的 bash 脚本文件。其中包含下载、安装和配置 Octopus Deploy 触手软件以及将其连接到 Octopus Deploy 服务器所需的命令:

#!/bin/bash
serverUrl="#{Project.Octopus.Server.Url}"
serverCommsPort="#{Project.Octopus.Server.PollingPort}"
apiKey="#{Project.Octopus.Server.ApiKey}"
name=$HOSTNAME
configFilePath="/etc/octopus/default/tentacle-default.config"
applicationPath="/home/Octopus/Applications/"
workerPool="#{Project.Octopus.Server.WorkerPool}"
machinePolicy="#{Project.Octopus.Server.MachinePolicy}"
space="#{Project.Octopus.Server.Space}"

sudo apt-key adv --fetch-keys "https://apt.octopus.com/public.key"
sudo add-apt-repository "deb https://apt.octopus.com/ stretch main"
sudo apt-get update
sudo apt-get install tentacle

sudo /opt/octopus/tentacle/Tentacle create-instance --config "$configFilePath" --instance "$name"
sudo /opt/octopus/tentacle/Tentacle new-certificate --if-blank
sudo /opt/octopus/tentacle/Tentacle configure --noListen True --reset-trust --app "$applicationPath"
echo "Registering the worker $name with server $serverUrl"
sudo /opt/octopus/tentacle/Tentacle register-worker --server "$serverUrl" --apiKey "$apiKey" --name "$name"  --comms-style "TentacleActive" --server-comms-port $serverCommsPort --workerPool "$workerPool" --policy "$machinePolicy" --space "$space"
sudo /opt/octopus/tentacle/Tentacle service --install --start 

provider.tf

provider.tf 通知 Terraform 我们正在使用哪个提供程序。在我们的例子中,这就是 AWS:

provider "aws" {
  region     = "${var.AWS_REGION}"
} 

安全组. tf

在与云提供商打交道时,安全组就是防火墙规则。securitygroup.tf 文件包含 EC2 实例的入口和出口规则。在这种情况下,端口 22 和 3389 出于调试目的而打开,可以省略:

resource "aws_security_group" "allow-octopusserver" {
  vpc_id      = "${aws_vpc.worker_vpc.id}"
  name        = "allow-octopusserver"
  description = "Security group that allows traffic to the worker from the Octpus Server"
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port = 3389
    to_port = 3389
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "allow-octopusserver"
  }
} 

vars.tfs

该文件包含我们的 Terraform 脚本中使用的不同变量。在这个文件中,我们定义了哪个 Amazon 机器映像(AMI)用于我们的 EC2 实例。AMI 值被硬编码在这个文件中,但是它们可以很容易地用 Octopus Deploy 中的变量替换,比如使用Octopus tache 语法替换AWS_REGION变量。该文件定义了以下变量:

  • AWS_REGION:我们正在使用的 AWS 区域。
  • LINUX_AMIS:我们的 Linux EC2 实例的 AMI。
  • WINDOWS_AMIS:我们的 Windows EC2 实例的 AMI。
  • PATH_TO_PRIVATE_KEY:与 AWS 密钥对相关联的私有密钥的路径,用于登录我们的实例(用于调试,不包括在内)。
  • PATH_TO_PUBLIC_KEY:与 AWS 密钥对相关联的公共密钥的路径,该密钥对用于登录我们的实例(用于调试,不包括在内)。
variable "AWS_REGION" {
  default = "#{Project.AWS.Region}"
}

variable "LINUX_AMIS" {
  default =  "ami-084a6c14d8630bb68"
}

variable "WINDOWS_AMIS"{
  default = "ami-087ee25b86edaf4b1"
}

variable "PATH_TO_PRIVATE_KEY" {
  default = "mykey"
}

variable "PATH_TO_PUBLIC_KEY" {
  default = "mykey.pub"
} 

vpc.tf

该文件包含在 AWS 中创建虚拟私有云(VPC)所需的资源。它包含以下内容的定义:

  • VPC
  • 子网
  • 互联网网关
  • 路由表
  • 路由表关联
# Internet VPC
resource "aws_vpc" "worker_vpc" {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
  enable_classiclink   = "false"
  tags = {
    Name = "worker_vpc"
  }
}

# Subnets
resource "aws_subnet" "worker-public-1" {
  vpc_id                  = "${aws_vpc.worker_vpc.id}"
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = "true"
  availability_zone       = "${var.AWS_REGION}a"

  tags = {
    Name = "worker-public-1"
  }
}

resource "aws_subnet" "worker-public-2" {
  vpc_id                  = "${aws_vpc.worker_vpc.id}"
  cidr_block              = "10.0.2.0/24"
  map_public_ip_on_launch = "true"
  availability_zone       = "${var.AWS_REGION}b"

  tags = {
    Name = "worker-public-2"
  }
}

resource "aws_subnet" "worker-public-3" {
  vpc_id                  = "${aws_vpc.worker_vpc.id}"
  cidr_block              = "10.0.3.0/24"
  map_public_ip_on_launch = "true"
  availability_zone       = "${var.AWS_REGION}c"

  tags = {
    Name = "worker-public-3"
  }
}

resource "aws_subnet" "worker-private-1" {
  vpc_id                  = "${aws_vpc.worker_vpc.id}"
  cidr_block              = "10.0.4.0/24"
  map_public_ip_on_launch = "false"
  availability_zone       = "${var.AWS_REGION}a"

  tags = {
    Name = "worker-private-1"
  }
}

resource "aws_subnet" "worker-private-2" {
  vpc_id                  = "${aws_vpc.worker_vpc.id}"
  cidr_block              = "10.0.5.0/24"
  map_public_ip_on_launch = "false"
  availability_zone       = "${var.AWS_REGION}b"

  tags = {
    Name = "worker-private-2"
  }
}

resource "aws_subnet" "worker-private-3" {
  vpc_id                  = "${aws_vpc.worker_vpc.id}"
  cidr_block              = "10.0.6.0/24"
  map_public_ip_on_launch = "false"
  availability_zone       = "${var.AWS_REGION}c"

  tags = {
    Name = "worker-private-3"
  }
}

# Internet GW
resource "aws_internet_gateway" "worker-gw" {
  vpc_id = "${aws_vpc.worker_vpc.id}"

  tags = {
    Name = "worker"
  }
}

# route tables
resource "aws_route_table" "worker-public" {
  vpc_id = "${aws_vpc.worker_vpc.id}"
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.worker-gw.id}"
  }

  tags = {
    Name = "worker-public-1"
  }
}

# route associations public
resource "aws_route_table_association" "worker-public-1-a" {
  subnet_id      = "${aws_subnet.worker-public-1.id}"
  route_table_id = "${aws_route_table.worker-public.id}"
}

resource "aws_route_table_association" "worker-public-2-a" {
  subnet_id      = "${aws_subnet.worker-public-2.id}"
  route_table_id = "${aws_route_table.worker-public.id}"
}

resource "aws_route_table_association" "worker-public-3-a" {
  subnet_id      = "${aws_subnet.worker-public-3.id}"
  route_table_id = "${aws_route_table.worker-public.id}"
} 

现在你知道了!创建 AWS 自动缩放工人大军所需的所有 Terraform 脚本。剩下的工作就是将这些文件打包成. zip 或。使用构建服务器或 Octopus CLI。在创建包并上传到我们的 Octopus 服务器后,我们可以将它包含在我们的部署过程中。

章鱼部署

建立 Terraform 文件是整个过程中最难的部分。在 Octopus Deploy 中创建部署的步骤非常短。出于我们的目的,我们只需要两个环境,SpinupTeardown

环境

要创建我们的环境,请导航至基础设施➜环境➜添加环境:

创建以下内容:

对于本演示,我们使用默认的生命周期。如果其他项目已经存在,可能有必要为此项目创建新的生命周期。

AWS 帐户

要使用 AWS,我们需要一个拥有创建我们的资源的必要权限的帐户。在 AWS 中创建之后,我们需要将这个帐户添加到 Octopus Deploy 中。为此,我们将点击基础设施➜帐户➜添加帐户:

选择 AWS 帐户:

填写访问密钥和密钥。请务必点击保存并测试以确保您的帐户正常工作:

工人池

在这篇文章中,我们将把有活力的员工放入他们自己的人才库。为此,我们首先创建池。

导航至基础设施➜工人池➜添加工人池:

为工人池命名并点击保存:

项目

随着环境的创建,我们可以创建我们的项目来创建我们的工人。

点击项目➜添加项目:

在您给它命名并点击保存后,您将被带到您的全新项目。

变量

我们上面创建的 Terraform 脚本有需要在部署时替换的八进制变量。要定义我们的变量,单击变量选项卡并创建以下变量(我用名字隔开我的变量,以便更容易识别它们来自哪里):

  • Project.AWS.Account:我们上面创建的 AWS 账户。
  • Project.AWS.Region:我们正在使用的 AWS 中的区域。
  • Project.AWS.S3.Bucket:我们将要存储状态文件的桶。
  • Project.AWS.S3.Key:存储我们状态的钥匙。
  • Project.Octopus.Server.ApiKey:将 worker 连接到 Octopus 服务器时使用的 API 密匙(敏感)。
  • Project.Octopus.Server.MachinePolicy:工作者将附加到的机器策略的名称。
  • Project.Octopus.Server.PollingPort:Octopus 服务器用于轮询触须的端口。
  • Project.Octopus.Server.Space:工作人员将被附加到的空间的名称(默认为空)。
  • Project.Octopus.Server.Url:八达通服务器网址。
  • Project.Octopus.Server.WorkerPool:工作者将加入的工作者池的名称。

步伐

我们的部署过程将包括两个步骤:地形应用步骤和地形破坏步骤。

Terraform 应用模板

点击处理➜添加步骤:

单击 Terraform 类别,然后选择应用 Terraform 模板:

为该步骤命名,并让它在默认池中的工作线程上执行:

启用帐户集成,选择 AWS 帐户并指定区域:

选择包含 Terraform 文件的包。取消选中默认 Terraform 文件中的替换变量。在目标文件部分,添加两个条目:

在条件下,仅在Spinup环境中执行该步骤:

【T2

这就是应用 Terraform 模板的步骤。

地形破坏模板

为了节约成本,当我们知道自己创造的资源不会被使用时,比如在一天工作结束时,我们可以把它们拆掉。在本演示中,我们选择将此作为我们部署的一部分,但是,我们可以轻松地将其实现为预定的运行手册

点击添加步骤,选择地形类别,然后选择破坏地形资源:

除了要在哪个环境中运行之外,为该模板填充与我们为应用 Terraform 模板所做的相同的值。对于销毁步骤,我们只希望它在Teardown环境中运行。

仅此而已!我们现在准备创建我们的版本并部署我们的 Terraform 模板。

应用模板

点击创建发布按钮:

点击保存:

SpinUp环境行上点击部署启动部署按钮;

最后,点击部署:

部署完成后,您将在动态工作线程池中看到四个工作线程。

Linux 工作者会比 Windows 工作者更早出现。如果一开始您没有看到四个工作人员,请给 Windows 工作人员多一点时间来完成资源调配。

结论

在这篇文章中,我带您创建了一些 Terraform 配置文件,这些文件将生成 AWS 自动伸缩组,以根据需求动态创建 Octopus Deploy worker 基础架构伸缩。

ECS - Octopus 部署的金丝雀部署

原文:https://octopus.com/blog/ecs-canary-deployments

Canary deployments with ECS

Canary 部署是一种流行的模式,它允许您向越来越多的最终用户逐步推出新版本的应用程序。通过在首次展示期间观察新版本的错误或不良影响,有可能在产品错误影响大多数用户之前捕捉并恢复它们。

ECS 具有对滚动更新的本地支持,其中服务中的任务是渐进的,但是是自动的,用应用程序的新版本进行更新。通过与 CodeDeploy 集成,可以执行 ECS 所谓的蓝/绿部署,尽管该部署选项可以配置为执行金丝雀部署,将流量转移到新版本。您甚至可以创建自己的部署策略,但是您受限于基于时间的金丝雀规则,即:

一种配置,以两个增量将流量从一个版本的 Lambda 函数或 ECS 任务集转移到另一个版本。

或者基于时间的线性规则,即:

一种配置,以相等的增量将流量从一个版本的 Lambda 函数或 ECS 任务集转移到另一个版本,每次增量之间的分钟数相等。

但是,有时候,在一段固定的时间内,将更多流量转移到 canary 部署的决定并不容易做出。例如,您可能需要一个人根据一系列输入(如支持请求、日志中的错误或资源使用情况)来决定是否继续进行 canary 部署。这种部署中的手动干预比 ECS 公开的蓝/绿策略需要更大的灵活性。

幸运的是, ECS 可以推迟向外部系统进行部署的决定。它需要一些工作来设置,但是非常灵活。

在这篇博文中,我们将看看如何使用 CloudFormation 管理 ECS canary 部署。

ECS 云形成资源

我们的 ECS 部署将通过 CloudFormation 创建和管理,并将利用以下资源:

最终的架构如下所示:

示例应用程序

为了演示 canary 部署,我们将使用从位于https://github . com/OctopusSamples/DockerHelloWorldWithVersion的代码创建的 Docker 映像。这是一个简单的“hello world”node . js 应用程序,它还打印了环境变量APPVERSION的值以响应 HTTP 请求。

AWS::ECS::TaskDefinition资源

任务定义将示例应用程序配置为侦听端口 4000 上的流量。它还定义了一个名为APPVERSION的环境变量,该变量将显示在响应中。

我们将更新APPVERSION环境变量,作为模拟新应用程序版本部署的一种方式。

这是第一个任务定义。注意UpdateReplacePolicy属性被设置为Retain。这意味着 CloudFormation 将创建一个新的任务定义,但不会删除(或者,从技术上讲,标记为非活动的)任何以前部署的任务定义:

 "MyTask": {
      "Type": "AWS::ECS::TaskDefinition",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "ContainerDefinitions": [
          {
            "Cpu": 256,
            "Image": "octopussamples/helloworldwithversion",
            "Memory": 512,
            "MemoryReservation": 128,
            "Name": "mycontainer",
            "Environment": [
              {
                "Name": "APPVERSION",
                "Value": "1.0.0"
              }
            ],
            "PortMappings": [
              {
                "ContainerPort": 4000,
                "HostPort": 4000,
                "Protocol": "tcp"
              }
            ]
          }
        ],
        "Cpu": "256",
        "Family": "mytask",
        "Memory": "512",
        "RequiresCompatibilities": [
          "FARGATE"
        ],
        "NetworkMode": "awsvpc"
      }
    } 

AWS::ElasticLoadBalancingV2::TargetGroup资源

目标组定义了响应网络请求的下游服务。我们在这里不定义任何服务,因为 ECS 本身会在创建任务时将任务放入目标组,并在销毁任务时删除任务。

下面的两个目标组将包含蓝色(或现有部署)任务和绿色(或新部署)任务:

 "GreenTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckEnabled": true,
        "HealthCheckIntervalSeconds": 5,
        "HealthCheckPath": "/",
        "HealthCheckPort": "4000",
        "HealthCheckProtocol": "HTTP",
        "HealthCheckTimeoutSeconds": 2,
        "HealthyThresholdCount": 2,
        "Matcher": {
          "HttpCode": "200"
        },
        "Name": "OctopusGreenTargetGroup",
        "Port": 4000,
        "Protocol": "HTTP",
        "TargetType": "ip",
        "UnhealthyThresholdCount": 5,
        "VpcId": "vpc-04fb5b2e72c17ca68"
      }
    } 
 "BlueTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckEnabled": true,
        "HealthCheckIntervalSeconds": 5,
        "HealthCheckPath": "/",
        "HealthCheckPort": "4000",
        "HealthCheckProtocol": "HTTP",
        "HealthCheckTimeoutSeconds": 2,
        "HealthyThresholdCount": 2,
        "Matcher": {
          "HttpCode": "200"
        },
        "Name": "OctopusBlueTargetGroup",
        "Port": 4000,
        "Protocol": "HTTP",
        "TargetType": "ip",
        "UnhealthyThresholdCount": 5,
        "VpcId": "vpc-04fb5b2e72c17ca68"
      }
    } 

AWS::ECS::TaskSet资源

使用标准滚动部署,ECS 服务将仅引用一个任务定义,服务资源将定义所有相关设置,如网络和任务计数。

任务集提供了一种更灵活的方法来定义服务中的任务定义。当使用任务集时,服务资源定义很少的设置,并且主要作为其子任务集的父资源存在。

在这个例子中,每个任务集指向相同的任务定义。这意味着对于初始部署,堆栈的蓝色和绿色部分是相同的。在本文的后面,我们将了解如何使用新的任务定义更新该模板,以便绿色堆栈执行金丝雀或蓝/绿部署:

 "GreenTaskSet": {
      "Type": "AWS::ECS::TaskSet",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ExternalId": "OctopusGreenStack",
        "LaunchType": "FARGATE",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "AssignPublicIp": "ENABLED",
            "SecurityGroups": [
              "sg-043789abf52c12d9a"
            ],
            "Subnets": [
              "subnet-0af41f8e0404d7b23",
              "subnet-0c2515119bdf77d4c",
              "subnet-09d1a3362fac596a9"
            ]
          }
        },
        "LoadBalancers": [
          {
            "ContainerName": "mycontainer",
            "ContainerPort": 4000,
            "TargetGroupArn": {
              "Ref": "GreenTargetGroup"
            }
          }
        ],
        "Scale": {
          "Unit": "PERCENT",
          "Value": 100
        },
        "Service": "myservice",
        "TaskDefinition": {"Ref": "MyTask10d4f29d4aa0474dbd4b0435922cd032"}
      },
      "DependsOn": [
        "MyService",
        "GreenTargetGroup"
      ]
    } 
 "BlueTaskSet": {
      "Type": "AWS::ECS::TaskSet",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ExternalId": "OctopusBlueStack",
        "LaunchType": "FARGATE",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "AssignPublicIp": "ENABLED",
            "SecurityGroups": [
              "sg-043789abf52c12d9a"
            ],
            "Subnets": [
              "subnet-0af41f8e0404d7b23",
              "subnet-0c2515119bdf77d4c",
              "subnet-09d1a3362fac596a9"
            ]
          }
        },
        "LoadBalancers": [
          {
            "ContainerName": "mycontainer",
            "ContainerPort": 4000,
            "TargetGroupArn": {
              "Ref": "BlueTargetGroup"
            }
          }
        ],
        "Scale": {
          "Unit": "PERCENT",
          "Value": 100
        },
        "Service": "myservice",
        "TaskDefinition": {"Ref": "MyTask10d4f29d4aa0474dbd4b0435922cd032"}
      },
      "DependsOn": [
        "MyService",
        "BlueTargetGroup"
      ]
    } 

AWS::ECS::Service资源

该服务将确保运行所需数量的任务,并且它们将继续运行。通常,我们会将服务配置为直接引用任务定义,但是在我们的例子中,我们将使用上面的任务集将服务链接到任务定义。这意味着我们的服务很少。

我们确实给服务添加了一个标记,以指示保存先前稳定部署的堆栈。在部署这个模板时,我们总是认为蓝色堆栈是以前的稳定部署,绿色堆栈是新的部署。然而,从蓝色到绿色的转换将涉及更新该标签,以指示绿色堆栈通过了测试,并且是新的稳定堆栈:

 "MyService": {
      "Type": "AWS::ECS::Service",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ServiceName": "myservice",
        "DeploymentController": {
          "Type": "EXTERNAL"
        },
        "Tags": [
          {
            "Key": "StableStack",
            "Value": "Blue"
          }
        ]
      }
    } 

AWS::ElasticLoadBalancingV2::Listener资源

监听器连接到负载平衡器,并定义用于将流量定向到目标组的协议和端口规则。下面的侦听器被配置为在端口 80 上接收 HTTP 流量,并且有一个默认规则,如果没有其他自定义规则与传入的请求匹配,则用 HTTP 404 状态代码进行响应。

请注意,我们对现有的 ALBNLB 的 ARN 进行了硬编码。这里的要求是现有的负载平衡器还没有配置使用端口 80 的侦听器:

 "MyListener": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "DefaultActions": [
          {
            "FixedResponseConfig": {
              "StatusCode": "404"
            },
            "Order": 1,
            "Type": "fixed-response"
          }
        ],
        "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:968802670493:loadbalancer/app/mattctest/3a1496378bd20439",
        "Port": 80,
        "Protocol": "HTTP"
      }
    } 

AWS::ElasticLoadBalancingV2::ListenerRule资源

侦听器规则附加到侦听器上,并提供对如何匹配传入请求以及如何将请求转发到目标组的细粒度控制。下面的侦听器规则匹配所有请求路径(本质上匹配所有请求),并在蓝色和绿色目标组之间划分流量。

修改分配给蓝色和绿色目标组的权重是我们实现蓝色/绿色或淡黄色部署的方式。从蓝堆栈到绿堆栈的硬切换实现了传统的蓝/绿部署,而逐渐增加到绿堆栈的流量实现了金丝雀部署:

 "MyListenerRule": {
      "Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
      "Properties": {
        "Actions": [
          {
            "ForwardConfig": {
              "TargetGroups": [
                {
                  "TargetGroupArn": {
                    "Ref": "GreenTargetGroup"
                  },
                  "Weight": 0
                },
                {
                  "TargetGroupArn": {
                    "Ref": "BlueTargetGroup"
                  },
                  "Weight": 100
                }
              ]
            },
            "Order": 1,
            "Type": "forward"
          }
        ],
        "Conditions": [
          {
            "Field": "path-pattern",
            "PathPatternConfig": {
              "Values": [
                "/*"
              ]
            }
          }
        ],
        "ListenerArn": {
          "Ref": "MyListener"
        },
        "Priority": 10
      },
      "DependsOn": [
        "MyListener",
        "GreenTargetGroup",
        "BlueTargetGroup"
      ]
    } 

完整的模板

这是完整的云形成模板:

{
  "Resources": {
    "MyTask": {
      "Type": "AWS::ECS::TaskDefinition",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "ContainerDefinitions": [
          {
            "Cpu": 256,
            "Image": "octopussamples/helloworldwithversion",
            "Memory": 512,
            "MemoryReservation": 128,
            "Name": "mycontainer",
            "Environment": [
              {
                "Name": "APPVERSION",
                "Value": "1.0.0"
              }
            ],
            "PortMappings": [
              {
                "ContainerPort": 4000,
                "HostPort": 4000,
                "Protocol": "tcp"
              }
            ]
          }
        ],
        "Cpu": "256",
        "Family": "mytask",
        "Memory": "512",
        "RequiresCompatibilities": [
          "FARGATE"
        ],
        "NetworkMode": "awsvpc"
      }
    },   
    "GreenTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckEnabled": true,
        "HealthCheckIntervalSeconds": 5,
        "HealthCheckPath": "/",
        "HealthCheckPort": "4000",
        "HealthCheckProtocol": "HTTP",
        "HealthCheckTimeoutSeconds": 2,
        "HealthyThresholdCount": 2,
        "Matcher": {
          "HttpCode": "200"
        },
        "Name": "OctopusGreenTargetGroup",
        "Port": 4000,
        "Protocol": "HTTP",
        "TargetType": "ip",
        "UnhealthyThresholdCount": 5,
        "VpcId": "vpc-04fb5b2e72c17ca68"
      }
    },
    "BlueTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckEnabled": true,
        "HealthCheckIntervalSeconds": 5,
        "HealthCheckPath": "/",
        "HealthCheckPort": "4000",
        "HealthCheckProtocol": "HTTP",
        "HealthCheckTimeoutSeconds": 2,
        "HealthyThresholdCount": 2,
        "Matcher": {
          "HttpCode": "200"
        },
        "Name": "OctopusBlueTargetGroup",
        "Port": 4000,
        "Protocol": "HTTP",
        "TargetType": "ip",
        "UnhealthyThresholdCount": 5,
        "VpcId": "vpc-04fb5b2e72c17ca68"
      }
    },
    "GreenTaskSet": {
      "Type": "AWS::ECS::TaskSet",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ExternalId": "OctopusGreenStack",
        "LaunchType": "FARGATE",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "AssignPublicIp": "ENABLED",
            "SecurityGroups": [
              "sg-043789abf52c12d9a"
            ],
            "Subnets": [
              "subnet-0af41f8e0404d7b23",
              "subnet-0c2515119bdf77d4c",
              "subnet-09d1a3362fac596a9"
            ]
          }
        },
        "LoadBalancers": [
          {
            "ContainerName": "mycontainer",
            "ContainerPort": 4000,
            "TargetGroupArn": {
              "Ref": "GreenTargetGroup"
            }
          }
        ],
        "Scale": {
          "Unit": "PERCENT",
          "Value": 100
        },
        "Service": "myservice",
        "TaskDefinition": {"Ref": "MyTask"}
      },
      "DependsOn": [
        "MyService",
        "GreenTargetGroup"
      ]
    },
    "BlueTaskSet": {
      "Type": "AWS::ECS::TaskSet",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ExternalId": "OctopusBlueStack",
        "LaunchType": "FARGATE",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "AssignPublicIp": "ENABLED",
            "SecurityGroups": [
              "sg-043789abf52c12d9a"
            ],
            "Subnets": [
              "subnet-0af41f8e0404d7b23",
              "subnet-0c2515119bdf77d4c",
              "subnet-09d1a3362fac596a9"
            ]
          }
        },
        "LoadBalancers": [
          {
            "ContainerName": "mycontainer",
            "ContainerPort": 4000,
            "TargetGroupArn": {
              "Ref": "BlueTargetGroup"
            }
          }
        ],
        "Scale": {
          "Unit": "PERCENT",
          "Value": 100
        },
        "Service": "myservice",
        "TaskDefinition": {"Ref": "MyTask"}
      },
      "DependsOn": [
        "MyService",
        "BlueTargetGroup"
      ]
    },
    "MyService": {
      "Type": "AWS::ECS::Service",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ServiceName": "myservice",
        "DeploymentController": {
          "Type": "EXTERNAL"
        },
        "Tags": [
          {
            "Key": "StableStack",
            "Value": "Blue"
          }
        ]
      }
    },
    "MyListener": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "DefaultActions": [
          {
            "FixedResponseConfig": {
              "StatusCode": "404"
            },
            "Order": 1,
            "Type": "fixed-response"
          }
        ],
        "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:968802670493:loadbalancer/app/mattctest/3a1496378bd20439",
        "Port": 80,
        "Protocol": "HTTP"
      }
    },
    "MyListenerRule": {
      "Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
      "Properties": {
        "Actions": [
          {
            "ForwardConfig": {
              "TargetGroups": [
                {
                  "TargetGroupArn": {
                    "Ref": "GreenTargetGroup"
                  },
                  "Weight": 0
                },
                {
                  "TargetGroupArn": {
                    "Ref": "BlueTargetGroup"
                  },
                  "Weight": 100
                }
              ]
            },
            "Order": 1,
            "Type": "forward"
          }
        ],
        "Conditions": [
          {
            "Field": "path-pattern",
            "PathPatternConfig": {
              "Values": [
                "/*"
              ]
            }
          }
        ],
        "ListenerArn": {
          "Ref": "MyListener"
        },
        "Priority": 10
      },
      "DependsOn": [
        "MyListener",
        "GreenTargetGroup",
        "BlueTargetGroup"
      ]
    }
  },
  "Outputs": {
    "BlueTaskSet": {
      "Description": "The blue service task set",
      "Value": {"Fn::GetAtt": ["BlueTaskSet", "Id"]}
    },
    "GreenTaskSet": {
      "Description": "The green service task set",
      "Value": {"Fn::GetAtt": ["GreenTaskSet", "Id"]}
    },
    "BlueTargetGroup": {
      "Description": "The blue target group",
      "Value": {"Ref": "BlueTargetGroup"}
    },
    "GreenTargetGroup": {
      "Description": "The green target group",
      "Value": {"Ref": "GreenTargetGroup"}
    }
  }
} 

部署这个堆栈会在 ECS 中产生一个新的服务。

运行的 2/1 任务的报告可能看起来有点奇怪,但是这个值是两个任务集的结果,每个任务集都被配置为运行其父服务所需任务计数的 100%,即 1。这意味着我们有两个任务集,每个任务集运行一个任务,从而为服务中定义的一个所需任务运行两个任务:

这两个任务是在蓝色和绿色任务集中配置的任务。请注意,任务集不会显示在控制台中,我们必须推断任务所属的任务集:

我们的负载平衡器侦听器已经配置为将流量定向到两个目标组:

ECS 向目标群体添加了以下任务:

由于这是我们的第一次部署,任务集都指向相同的任务定义,作为蓝/绿或淡黄色部署的一部分,没有什么可切换的。

此时,我们假设初始部署已经完成。这意味着蓝色堆栈表示当前部署的公开可用版本的应用程序接收 100%的流量,绿色堆栈未使用。

将新版本部署到绿色任务集

如果您还记得之前的内容,我们在服务中添加了一个标记来指示哪个堆栈(蓝色或绿色)是稳定堆栈。我们现在希望找到稳定堆栈的详细信息,并在开始新的部署时将它们复制回蓝色堆栈。

我们知道这个标签当前被设置为蓝色,因为这是我们设置的,没有任何东西改变它。然而,我们仍然希望以可重复的方式运行提取标记值的过程,因为下一个部署不能假设标记有已知的值。

下面的命令将提取标签的值:

STACK=`aws ecs list-tags-for-resource --resource-arn arn:aws:ecs:us-east-1:968802670493:service/mattctest/myservice | jq -r '.tags[] | select(.key == "StableStack") | .value'` 

然后,我们可以使用以下命令从 CloudFormation 模板输出中获取活动堆栈的任务集 ID:

TASKSET=`aws cloudformation describe-stacks --stack-name ecs-task-test-2 --query "Stacks[0].Outputs[?OutputKey=='${STACK}TaskSet'].OutputValue" --output text` 

最后,我们可以使用以下命令检查稳定任务集的状态:

aws ecs describe-task-sets --cluster "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest" --service myservice --task-sets "${TASKSET}" 

这将返回蓝色任务集的配置。该任务集定义了稳定任务集的状态:

{
    "taskSets": [
        {
            "id": "ecs-svc/8260773081660460393",
            "taskSetArn": "arn:aws:ecs:us-east-1:968802670493:task-set/mattctest/myservice/ecs-svc/8260773081660460393",
            "serviceArn": "arn:aws:ecs:us-east-1:968802670493:service/myservice",
            "clusterArn": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
            "externalId": "OctopusBlueStack",
            "status": "ACTIVE",
            "taskDefinition": "arn:aws:ecs:us-east-1:968802670493:task-definition/mytask:26",
            "computedDesiredCount": 1,
            "pendingCount": 0,
            "runningCount": 1,
            "createdAt": 1611869109.157,
            "updatedAt": 1611869219.178,
            "launchType": "FARGATE",
            "platformVersion": "1.3.0",
            "networkConfiguration": {
                "awsvpcConfiguration": {
                    "subnets": [
                        "subnet-0af41f8e0404d7b23",
                        "subnet-0c2515119bdf77d4c",
                        "subnet-09d1a3362fac596a9"
                    ],
                    "securityGroups": [
                        "sg-043789abf52c12d9a"
                    ],
                    "assignPublicIp": "ENABLED"
                }
            },
            "loadBalancers": [
                {
                    "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:968802670493:targetgroup/OctopusBlueTargetGroup/52d9822ce6afb1fd",
                    "containerName": "mycontainer",
                    "containerPort": 4000
                }
            ],
            "serviceRegistries": [],
            "scale": {
                "value": 100.0,
                "unit": "PERCENT"
            },
            "stabilityStatus": "STEADY_STATE",
            "stabilityStatusAt": 1611869219.178,
            "tags": []
        }
    ],
    "failures": []
} 

为了执行第二次部署,我们需要将稳定任务集状态中的任何相关值复制到我们自己的 CloudFormation 模板中定义的蓝色任务集资源中。特别是,我们希望保留任务集当前配置的任务定义。

如果需要更新其他细节,如容器名或端口,也值得考虑。然而,在我们的例子中,我们知道唯一需要复制的属性是TaskDefinition

将稳定任务集的状态复制到我们新的蓝色任务集中,将我们的部署重置为规范的蓝色/绿色状态,其中蓝色是稳定的部署,绿色是新的部署。

下面我们更新TaskDefinition属性来引用固定的任务定义版本:

 "BlueTaskSet": {
      "Type": "AWS::ECS::TaskSet",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ExternalId": "OctopusBlueStack",
        "LaunchType": "FARGATE",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "AssignPublicIp": "ENABLED",
            "SecurityGroups": [
              "sg-043789abf52c12d9a"
            ],
            "Subnets": [
              "subnet-0af41f8e0404d7b23",
              "subnet-0c2515119bdf77d4c",
              "subnet-09d1a3362fac596a9"
            ]
          }
        },
        "LoadBalancers": [
          {
            "ContainerName": "mycontainer",
            "ContainerPort": 4000,
            "TargetGroupArn": {
              "Ref": "BlueTargetGroup"
            }
          }
        ],
        "Scale": {
          "Unit": "PERCENT",
          "Value": 100
        },
        "Service": "myservice",
        "TaskDefinition": "arn:aws:ecs:us-east-1:968802670493:task-definition/mytask:26"
      },
      "DependsOn": [
        "MyService",
        "BlueTargetGroup"
      ]
    } 

到目前为止,我们所做的就是用一个固定的TaskDefinition重新创建蓝色任务集。这似乎是多余的,但是找到稳定的任务集并基于其值配置新的蓝色堆栈的过程将允许我们在执行(并且可能失败)蓝色/绿色或金丝雀部署时在蓝色和绿色堆栈之间来回循环。

现在让我们配置一个新版本的任务定义。我们将通过更新APPVERSION环境变量来演示这个新版本:

 "MyTask": {
      "Type": "AWS::ECS::TaskDefinition",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "ContainerDefinitions": [
          {
            "Cpu": 256,
            "Image": "octopussamples/helloworldwithversion",
            "Memory": 512,
            "MemoryReservation": 128,
            "Name": "mycontainer",
            "Environment": [
              {
                "Name": "APPVERSION",
                "Value": "1.0.1"
              }
            ],
            "PortMappings": [
              {
                "ContainerPort": 4000,
                "HostPort": 4000,
                "Protocol": "tcp"
              }
            ]
          }
        ],
        "Cpu": "256",
        "Family": "mytask",
        "Memory": "512",
        "RequiresCompatibilities": [
          "FARGATE"
        ],
        "NetworkMode": "awsvpc"
      }
    } 

这个新任务定义是在绿色任务集中配置的。以下是新部署的完整模板:

{
  "Resources": {
    "MyTask": {
      "Type": "AWS::ECS::TaskDefinition",
      "UpdateReplacePolicy": "Retain",
      "Properties": {
        "ContainerDefinitions": [
          {
            "Cpu": 256,
            "Image": "octopussamples/helloworldwithversion",
            "Memory": 512,
            "MemoryReservation": 128,
            "Name": "mycontainer",
            "Environment": [
              {
                "Name": "APPVERSION",
                "Value": "1.0.1"
              }
            ],
            "PortMappings": [
              {
                "ContainerPort": 4000,
                "HostPort": 4000,
                "Protocol": "tcp"
              }
            ]
          }
        ],
        "Cpu": "256",
        "Family": "mytask",
        "Memory": "512",
        "RequiresCompatibilities": [
          "FARGATE"
        ],
        "NetworkMode": "awsvpc"
      }
    },   
    "GreenTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckEnabled": true,
        "HealthCheckIntervalSeconds": 5,
        "HealthCheckPath": "/",
        "HealthCheckPort": "4000",
        "HealthCheckProtocol": "HTTP",
        "HealthCheckTimeoutSeconds": 2,
        "HealthyThresholdCount": 2,
        "Matcher": {
          "HttpCode": "200"
        },
        "Name": "OctopusGreenTargetGroup",
        "Port": 4000,
        "Protocol": "HTTP",
        "TargetType": "ip",
        "UnhealthyThresholdCount": 5,
        "VpcId": "vpc-04fb5b2e72c17ca68"
      }
    },
    "BlueTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckEnabled": true,
        "HealthCheckIntervalSeconds": 5,
        "HealthCheckPath": "/",
        "HealthCheckPort": "4000",
        "HealthCheckProtocol": "HTTP",
        "HealthCheckTimeoutSeconds": 2,
        "HealthyThresholdCount": 2,
        "Matcher": {
          "HttpCode": "200"
        },
        "Name": "OctopusBlueTargetGroup",
        "Port": 4000,
        "Protocol": "HTTP",
        "TargetType": "ip",
        "UnhealthyThresholdCount": 5,
        "VpcId": "vpc-04fb5b2e72c17ca68"
      }
    },
    "GreenTaskSet": {
      "Type": "AWS::ECS::TaskSet",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ExternalId": "OctopusGreenStack",
        "LaunchType": "FARGATE",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "AssignPublicIp": "ENABLED",
            "SecurityGroups": [
              "sg-043789abf52c12d9a"
            ],
            "Subnets": [
              "subnet-0af41f8e0404d7b23",
              "subnet-0c2515119bdf77d4c",
              "subnet-09d1a3362fac596a9"
            ]
          }
        },
        "LoadBalancers": [
          {
            "ContainerName": "mycontainer",
            "ContainerPort": 4000,
            "TargetGroupArn": {
              "Ref": "GreenTargetGroup"
            }
          }
        ],
        "Scale": {
          "Unit": "PERCENT",
          "Value": 100
        },
        "Service": "myservice",
        "TaskDefinition": {"Ref": "MyTask"}
      },
      "DependsOn": [
        "MyService",
        "GreenTargetGroup"
      ]
    },
    "BlueTaskSet": {
      "Type": "AWS::ECS::TaskSet",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ExternalId": "OctopusBlueStack",
        "LaunchType": "FARGATE",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "AssignPublicIp": "ENABLED",
            "SecurityGroups": [
              "sg-043789abf52c12d9a"
            ],
            "Subnets": [
              "subnet-0af41f8e0404d7b23",
              "subnet-0c2515119bdf77d4c",
              "subnet-09d1a3362fac596a9"
            ]
          }
        },
        "LoadBalancers": [
          {
            "ContainerName": "mycontainer",
            "ContainerPort": 4000,
            "TargetGroupArn": {
              "Ref": "BlueTargetGroup"
            }
          }
        ],
        "Scale": {
          "Unit": "PERCENT",
          "Value": 100
        },
        "Service": "myservice",
        "TaskDefinition": "arn:aws:ecs:us-east-1:968802670493:task-definition/mytask:26"
      },
      "DependsOn": [
        "MyService",
        "BlueTargetGroup"
      ]
    },
    "MyService": {
      "Type": "AWS::ECS::Service",
      "Properties": {
        "Cluster": "arn:aws:ecs:us-east-1:968802670493:cluster/mattctest",
        "ServiceName": "myservice",
        "DeploymentController": {
          "Type": "EXTERNAL"
        },
        "Tags": [
          {
            "Key": "StableStack",
            "Value": "Blue"
          }
        ]
      }
    },
    "MyListener": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "DefaultActions": [
          {
            "FixedResponseConfig": {
              "StatusCode": "404"
            },
            "Order": 1,
            "Type": "fixed-response"
          }
        ],
        "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:968802670493:loadbalancer/app/mattctest/3a1496378bd20439",
        "Port": 80,
        "Protocol": "HTTP"
      }
    },
    "MyListenerRule": {
      "Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
      "Properties": {
        "Actions": [
          {
            "ForwardConfig": {
              "TargetGroups": [
                {
                  "TargetGroupArn": {
                    "Ref": "GreenTargetGroup"
                  },
                  "Weight": 0
                },
                {
                  "TargetGroupArn": {
                    "Ref": "BlueTargetGroup"
                  },
                  "Weight": 100
                }
              ]
            },
            "Order": 1,
            "Type": "forward"
          }
        ],
        "Conditions": [
          {
            "Field": "path-pattern",
            "PathPatternConfig": {
              "Values": [
                "/*"
              ]
            }
          }
        ],
        "ListenerArn": {
          "Ref": "MyListener"
        },
        "Priority": 10
      },
      "DependsOn": [
        "MyListener",
        "GreenTargetGroup",
        "BlueTargetGroup"
      ]
    }
  },
  "Outputs": {
    "BlueTaskSet": {
      "Description": "The blue service task set",
      "Value": {"Fn::GetAtt": ["BlueTaskSet", "Id"]}
    },
    "GreenTaskSet": {
      "Description": "The green service task set",
      "Value": {"Fn::GetAtt": ["GreenTaskSet", "Id"]}
    },
    "BlueTargetGroup": {
      "Description": "The blue target group",
      "Value": {"Ref": "BlueTargetGroup"}
    },
    "GreenTargetGroup": {
      "Description": "The green target group",
      "Value": {"Ref": "GreenTargetGroup"}
    }
  }
} 

在部署了第二个模板之后,我们现在有了一个任务引用两个任务定义的服务。蓝色任务集指向以前的稳定部署,而绿色任务集指向新部署:

最终用户仍然无法访问新的部署,因为侦听器规则将 100%的流量定向到蓝色稳定堆栈。

要将 canary 部署向绿色堆栈推进,我们可以运行以下 AWS CLI 命令,该命令将更新权重,将 10%的流量导向绿色堆栈中的新部署:

aws elbv2 modify-rule \
  --rule-arn "arn:aws:elasticloadbalancing:us-east-1:968802670493:listener-rule/app/mattctest/3a1496378bd20439/b934cb81e0365dab/945df85599ee2912" \
  --actions '[{
    "Type": "forward",
    "Order": 10, 
    "ForwardConfig": {
      "TargetGroups": [
        { 
          "Weight": 90, 
          "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:968802670493:targetgroup/OctopusBlueTargetGroup/52d9822ce6afb1fd" 
        },
        { 
          "Weight": 10, 
          "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:968802670493:targetgroup/OctopusGreenTargetGroup/f52cb2839cc063d9" 
        }
      ]
    }
  }]' 

随着稳定的蓝色任务集和新的绿色任务集之间的流量分离,我们现在可以运行我们需要的任何测试来验证新部署是否按预期工作。与 CodeDeploy 驱动的蓝/绿部署不同,没有从蓝到绿的自动进展,我们可以完全控制在堆栈之间以及何时拆分多少流量。

权重可以递增地更新,以将更多流量驱动到绿色堆栈,最终达到绿色堆栈接收 100%流量的点。此时,绿色堆栈代表稳定堆栈。我们通过以下命令将这一变化反映在分配给服务的标记中:

aws ecs tag-resource --resource-arn arn:aws:ecs:us-east-1:968802670493:service/mattctest/myservice --tags key=StableStack,value=Green 

如果我们发现新的绿色堆栈有错误,我们只需将所有流量返回到蓝色堆栈。 StableStack 标签仍然设置为蓝色,我们可以再次运行该流程。只要我们每次创建 CloudFormation 模板时,我们都使用由 StableStack 标签标识的旧稳定堆栈的详细信息来重新创建新的蓝色堆栈,最后一次部署是否成功过渡到绿色堆栈并不重要,因为 StableStack 标签将为我们指出什么是最后一次稳定堆栈颜色。

此时,我们可以返回并再次运行该流程:

  1. 找到分配给 StableStack 标签的值。
  2. 从 CloudFormation 输出中获取稳定任务集的 ID。
  3. 查询稳定任务集的当前状态。
  4. 用新的任务定义更新 CloudFormation 模板,并将蓝色任务集配置为匹配当前的稳定任务集。
  5. 将流量从蓝色目标群体导向绿色目标群体。
  6. 如果一切顺利,将 100%的流量导向绿色堆栈,并将 StableStack 标签设置为绿色
  7. 如果发现问题,将 100%的流量导向蓝色堆栈,并将 StableStack 标记为蓝色
  8. 转到步骤 1。

绿色堆栈的高级测试

如果您仔细观察为在蓝栈和绿栈之间划分流量而创建的侦听器规则,您会发现我们已经在10定义了它的优先级。这些优先级编号有点像旧的基本应用程序中的命令编号,您总是以 10 为步长递增,以便为以后添加中间的步长留出空间。

在我们的例子中,将少量的主要流量导向绿色堆栈可能是有用的,但是也允许使用只有测试人员知道的 URL 来访问绿色堆栈。例如,您可以添加一个规则,仅在提供了特殊查询字符串时将流量定向到绿色堆栈。

如果查询字符串 test 的值为 true ,下面的命令将创建一个新规则,将流量定向到绿色堆栈:

aws elbv2 create-rule \
  --listener-arn "arn:aws:elasticloadbalancing:us-east-1:968802670493:listener/app/mattctest/3a1496378bd20439/b934cb81e0365dab" \
  --priority 5 \
  --conditions '[{
    "Field": "query-string",
    "QueryStringConfig": {
      "Values": [
        {
          "Key": "test",
          "Value": "true"
        }
      ]
    }
  }]' \
  --actions '[{
    "Type": "forward",
    "Order": 10, 
    "ForwardConfig": {
      "TargetGroups": [
        { 
          "Weight": 100, 
          "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:968802670493:targetgroup/OctopusGreenTargetGroup/f52cb2839cc063d9" 
        }
      ]
    }
  }]' 

请注意,在 UI 中,规则以并发优先级显示,因此即使我们创建了优先级为 5 和 10 的规则,它们在 UI 中也显示为规则 1 和 2:

我们现在可以用类似http://mattctest-314950320.us-east-1.elb.amazonaws.com/?的 URL 测试绿色堆栈测试=真。不管我们的 CloudFormation 模板定义的规则中的流量划分如何,我们都将被定向到绿色堆栈进行测试。

结论

ECS 通过 CodeDeploy 支持许多现成的有用部署选项。然而,canary 部署的进展遵循一个模型,该模型假设所有流量将从旧部署切换到新部署,除非您明确中断它。

通过利用 ECS 中的外部部署策略,可以构建允许手动进行新部署的任务集。当由可能必须审查新部署的状态或获得将更多流量定向到新部署的许可的人明确做出切换流量的决定时,这是有用的。

在本帖中,我们查看了一个示例 ECS CloudFormation 模板,该模板定义了一个外部部署并通过一个负载均衡器提供了一个定制的流量分割,并描述了一个允许逐步手动部署新部署的流程。

最终结果是一个可重复的部署过程,支持蓝/绿和金丝雀部署,支持手动测试和显式流量切换。

愉快的部署!

使用 ECR - Octopus Deploy 中的容器部署 AWS ECS 任务

原文:https://octopus.com/blog/ecs

Octopus Juggling AWS ECS and Docker

Amazon 的弹性容器服务(ECS)提供了一种简化的方式来编排 Docker 容器的运行,这是 Kubernetes 的一种流行替代方案。通过从版本2018.8.0开始的 Octopus Deploy 中可用的多包脚本步骤,您现在可以使用 Octopus 的版本控制和变量管理的所有好处来指导 ECS 的部署。这个版本还支持 Amazon 的弹性容器注册中心(ECR)作为一级提要类型。为了演示这可能如何为您工作,下面的帖子通过一个例子从 Dockerfile 到 Deployment。

但首先要了解一些背景。

弹性集装箱服务

在 Kubernetes 成为容器编排的领导者之前,AWS 提出了自己的抽象,帮助管理跨多个容器实例的伸缩和负载平衡。ECS 定义了容器部署的配置,这种方式感觉上更接近于他们如何接近 AWS Lambdas 类似于 Lambdas,主配置在每次更新时都被版本化,并且服务提供了一种抽象,它(类似于 Lambda 的别名)可以指向特定版本。这些服务都位于一个由节点组成的集群中,在此之前,这些节点是您必须管理的一堆 EC2 实例。随着 Fargate 的发布,AWS 现在将高兴地完全抽象和管理单个机器。

Tasks, Services, and Clusters

服务在集群中运行,它们的活动任务基于特定版本的任务定义。

弹性容器注册

自从 2016 年第一个 Docker 步骤可用以来,在 Octopus Deploy 中使用 Docker 容器注册表的能力已经成为可能。尽管 AWS 在弹性容器注册中心(ECR)下提供了 Docker 容器注册中心,但是有一个小小的障碍使得使用它非常困难。Octopus 集成的标准 V2 Docker 注册 API 采用用户名和密码进行身份验证。尽管 ECR 没有提供静态的凭证集,但是它们通过一个get-login API 请求提供了登录细节。然而,问题是这些凭证只在 12 小时内有效。这意味着要在 Octopus Deploy 中使用 ECR 提要,您需要确保至少每 12 小时检索一次凭证并更新提要细节。对于任何构建自动化部署管道的尝试来说,这显然有点扫兴。

2018.8.0版本中,我们提供了一种将 AWS ECR 提要添加为一级提要类型的方法。通过提供适当的 AWS 凭证,Octopus 可以处理这个两步认证过程,这样您就可以使用标准的 IAM 角色。

使用 Octopus 的部署

为 ECR 建立形象

我们的示例图像是一个基本的 HTML 网站,它构建在httpd容器图像之上,并根据我们计划提供的一些环境变量更改其内容:

FROM httpd:2.4

ENV OCTO_COLOR="#f00"
ENV OCTO_ENV="DEFAULT"
CMD echo "<html>\
    <head>\
        <title>Octopus Container</title>\
        <style>body {background-color: $OCTO_COLOR;} </style>\
    </head>\
    <body><h1>Hello $OCTO_ENV</h1></body>\
</html>" > /usr/local/apache2/htdocs/index.html && httpd-foreground\ 

像任何好的连续部署系统一样,我们希望构建一次并部署多次,所以在构建阶段,构建映像将在 Octopus Deploy 之外进行。我建议阅读一些 AWS 的广泛的文档来了解将图像推送到 ECR 的细节。

将 ECR 馈送添加到 Octopus 部署

准备好部署我们的映像后,我们可以继续将 ECR 添加到 Octopus 作为一级提要类型。

Library部分,添加一个新进给并选择类型AWS Elastic Container Registry。然后,您需要提供您的 AWS 凭证和注册中心所在的区域。

ECR Feed

当我们保存并测试新的提要时,Octopus 应该会找到我们之前配置的注册表。Octopus 正在做的是使用提供的凭证联系 AWS,并获得 Docker 用来与远程存储库交互的标准用户名/密码凭证。然后,它使用检索到的用户名和密码与 AWS 公开的 v2 Docker 注册表进行交互,就像标准的 Docker 提要类型一样。

因为 Octopus 服务器本身需要访问注册表来列出图像和标签(当创建一个发布时作为版本),所以假定的 IAM 角色目前不支持这个提要类型。虽然这个将来可能会得到支持,但它也需要 Octopus 服务器本身运行在 AWS 基础设施中才能工作。目前,AWS ECR 提要类型需要标准的 AWS 凭证才能运行。

将映像部署到 AWS ECS

尽管 Octopus 目前没有特定于 ECS 的部署步骤,但我们仍然可以利用多包脚本步骤来更新我们的 ECS 任务和服务。这将允许我们使用 Octopus 来控制整个部署管道中发布的映像版本,以及管理需要提供给运行容器的不同变量。向您的项目添加一个Run an AWS CLI Script步骤。

Project Step

输入 ECR 服务所在的 AWS 区域,并选择拥有创建 ECR 任务和更新 ECR 服务所需权限的 AWS 帐户。该帐户可能会因阶段和生产而异,因此最好通过一个适用于不同环境的项目变量来提供该帐户。

跳过Referenced Packages部分,添加我们添加到 ECR 提要中的 Docker 图像。对于这个映像,我们不需要进行任何包获取,因为这将由 AWS 自己处理,所以选择The package will not be acquired选项。我们还为它取了一个简单的名称,我们将使用它来访问脚本中的这些变量,但是,这个字段可以留空,然后它将默认为 packageId。

Reference Package

当在脚本中引用一个包时,我们可以访问一堆变量,这些变量由我们上面提供的名字索引。

  • Octopus.Action.Package[web].PackageId:包 ID。对于 docker 图像,这大致与存储库名称(“hello-color”)相关。
  • Octopus.Action.Package[web].PackageVersion:发布中包含的软件包版本。在 docker 图像的情况下,这与图像标签相关。
  • 提要 ID("提要-新加坡-ECR ").

还提供了一个 docker 特定变量Octopus.Action.Package[web].Image,它解析为完全限定的图像名。在这个包的例子中,它可能看起来有点像918801671493.dkr.ecr.ap-southeast-1.amazonaws.com/hello-color:1.0.1。我们需要在下面的脚本中使用这个Image变量。

我们可以将脚本分成 3 个基本部分:

1.定义容器

$PortMappings = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.PortMapping]"
$PortMappings.Add($(New-Object -TypeName "Amazon.ECS.Model.PortMapping" -Property @{ HostPort=80; ContainerPort=80; Protocol=[Amazon.ECS.TransportProtocol]::Tcp}))

$EnvironmentVariables = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.KeyValuePair]"
$EnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="OCTO_COLOR"; Value=$OctopusParameters["Color"]}))
$EnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="OCTO_MSG"; Value=$OctopusParameters["Message"]}))

Write-Host "Adding Container Definition for" $OctopusParameters["Octopus.Action.Package[web].Image"]
$ContainerDefinitions = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.ContainerDefinition]"
$ContainerDefinitions.Add($(New-Object -TypeName "Amazon.ECS.Model.ContainerDefinition" -Property @{ `
    Name="web"; `
    Image=$OctopusParameters["Octopus.Action.Package[web].Image"]; `
    PortMappings=$PortMappings; `
    Environment=$EnvironmentVariables
    Memory=256;})) 

我们必须在该任务的容器定义中显式设置环境变量。虽然在集群上直接运行任务时可以覆盖环境变量,但是当通过服务运行时,目前没有这样的选项,动态配置由用户根据环境元数据来决定。有一个公开的 ECS GitHub 问题要求更好的支持,然而,这个问题已经存在了将近四年,所以可能不会很快得到解决。因此,我建议为每个环境保留一个单独的任务,并根据部署使用项目变量来改变更新的任务。

注意,在提供图像细节时,我们使用了上面描述的Octopus.Action.Package[web].Image变量。该值将从发布期间选择的映像版本中获得。

2.使用容器定义创建任务

$Region = $OctopusParameters["Octopus.Action.Amazon.RegionName"]
$TaskName = $OctopusParameters["TaskName"]
$ExecutionRole = $(Get-IAMRole -RoleName "ecsTaskExecutionROle").Arn

Write-Host "Creating New Task Definition $TaskName"
$TaskDefinition = Register-ECSTaskDefinition `
    -ContainerDefinition $ContainerDefinitions `
    -Cpu 256 `
    -Family $TaskName `
    -TaskRoleArn $ExecutionRole `
    -ExecutionRoleArn $ExecutionRole `
    -Memory 512 `
    -NetworkMode awsvpc `
    -Region $Region `
    -RequiresCompatibility "FARGATE" 

虽然您可以加载一个以前构建的任务配置作为模板,并且只更新映像,但是这种方法确保 Octopus 部署过程成为预期运行的事实的来源。此外,这意味着该脚本可以在还没有设置之前的脚本的地方运行,例如,当您想要为新的测试环境配置新的端点时。

通过从环境变量中加载任务名称,我们可以根据环境(和租户,如果相关的话)改变任务,这允许我们为不同的部署上下文拥有多个任务定义。

3.升级服务以使用新任务

$ClusterName = $OctopusParameters["ClusterName"]
$ServiceName = $OctopusParameters["ServiceName"]

Write-Host "Updating Service $ServiceName"
$ServiceUpdate = Update-ECSService `
    -Cluster $ClusterName `
    -ForceNewDeployment $true `
    -Service $ServiceName `
    -TaskDefinition $TaskDefinition.TaskDefinitionArn `
    -DesiredCount 2 `
    -DeploymentConfiguration_MaximumPercent 200 `
    -DeploymentConfiguration_MinimumHealthyPercent 100 

为了让我们可以为每个环境运行多个服务,服务的名称是通过一个项目变量提供的,该项目变量使用随环境变化的命名约定(详见本文末尾的项目变量截屏)。

本文中描述的另一种方法可能是不使用服务,直接在集群中运行任务。这实际上只对特定的任务有用,而对可伸缩的应用程序没有用,因为使用服务可以更容易地设置更高级的配置,比如包括一个负载平衡器,以便在多个任务之间分配流量,或者设置自动伸缩规则。

把所有的放在一起

如果我们将所有这些脚本放在一起,并添加一些日志功能,它应该看起来像下面这样:

# Define Container
$PortMappings = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.PortMapping]"
$PortMappings.Add($(New-Object -TypeName "Amazon.ECS.Model.PortMapping" -Property @{ HostPort=80; ContainerPort=80; Protocol=[Amazon.ECS.TransportProtocol]::Tcp}))

$EnvironmentVariables = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.KeyValuePair]"
$EnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="OCTO_COLOR"; Value=$OctopusParameters["Color"]}))
$EnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="OCTO_MSG"; Value=$OctopusParameters["Message"]}))

Write-Host "Adding Container Definition for" $OctopusParameters["Octopus.Action.Package[web].Image"]
$ContainerDefinitions = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.ContainerDefinition]"
$ContainerDefinitions.Add($(New-Object -TypeName "Amazon.ECS.Model.ContainerDefinition" -Property @{ `
    Name="web"; `
    Image=$OctopusParameters["Octopus.Action.Package[web].Image"]; `
    PortMappings=$PortMappings; `
    Environment=$EnvironmentVariables
    Memory=256;}))

# Create Task
$Region = $OctopusParameters["Octopus.Action.Amazon.RegionName"]
$TaskName = $OctopusParameters["TaskName"]
$ExecutionRole = $(Get-IAMRole -RoleName  "ecsTaskExecutionROle").Arn
Write-Host "Creating New Task Definition $TaskName"
$TaskDefinition = Register-ECSTaskDefinition `
    -ContainerDefinition $ContainerDefinitions `
    -Cpu 256 `
    -Family $TaskName `
    -TaskRoleArn $ExecutionRole `
    -ExecutionRoleArn $ExecutionRole `
    -Memory 512 `
    -NetworkMode awsvpc `
    -Region $Region `
    -RequiresCompatibility "FARGATE"

if(!$?) {
    Write-Error "Failed to register new task definition"
    Exit 0
}
Write-Host "Created Task Definition $($TaskDefinition.TaskDefinitionArn)"
Write-Verbose $($TaskDefinition | ConvertTo-Json)

# Update Service
$ClusterName = $OctopusParameters["ClusterName"]
$ServiceName = $OctopusParameters["ServiceName"]
Write-Host "Updating Service $ServiceName"
$ServiceUpdate = Update-ECSService `
    -Cluster $ClusterName `
    -ForceNewDeployment $true `
    -Service $ServiceName `
    -TaskDefinition $TaskDefinition.TaskDefinitionArn `
    -DesiredCount 2 `
    -DeploymentConfiguration_MaximumPercent 200 `
    -DeploymentConfiguration_MinimumHealthyPercent 100
if(!$?) {
    Write-Error "Failed to register new task definition"
    Exit 0
}
Write-Host "Updated Service $($ServiceUpdate.ServiceArn)"
Write-Verbose $($ServiceUpdate | ConvertTo-Json) 

Script Step

然后,我们添加以下变量,这些变量为 ECS 基础设施本身和我们希望推入容器的细节提供配置。

Project Variables

部署

创建发布时,系统会提示您提供想要部署的映像版本。Octopus 直接从容器注册中心获得这些信息,因此您可以准确地看到哪些图像已经部署,哪些图像尚未发布。版本号实际上是通过解析图像标签获得的,因此尽管您可以提供任何标签作为“版本”,但是只有可以被解析为 semver 2 的标签才是可见的。

Create Release

开始部署时,您会注意到,虽然我们使用的是一个包(映像),但没有发生任何获取。这是因为 Octopus 只是提供描述包的值供我们的脚本使用。当部署执行时,ECS 服务将运行新任务,并根据DesiredCountDeploymentConfiguration_MaximumPercentDeploymentConfiguration_MinimumHealthyPercent配置,确保在任何给定时间点都有正确数量的任务处于活动状态。这导致滚动更新风格的部署。

让我们看一下我们的开发和生产部署:

Dev

Prod

Huzzzah!颜色和信息,当我考虑这个应用程序的流量时,我很高兴我们有这样的负载平衡!

留给读者的活动

这个部署脚本可能比您在现实世界中想要的要简单得多。您可能需要配置卷装载、CPU 限制、自定义缩放规则或 AWS APIs 公开的任意数量的各种配置选项。该脚本主要关注“Fargate”产品,该产品抽象了集群中服务器的管理,但是,如果您使用 ECS 的“EC2”配置,同样的原理只需稍加修改即可。

Octopus 部署 ECS 的未来计划

虽然 Kubernetes 最近在容器领域引起了业界的注意,但 ECS 仍然是一个受欢迎的选择,我们预计可能会有更多一流的 ECS 步骤出现。根据“Octopus 是真理之源”的理念,我希望 ECS 的任何特定步骤最终都能够反映目前通过 AWS 门户可获得的许多配置选项,但是,与 Octopus 变量和包选择的集成更加紧密。

Octopus 的每个版本都提供了与各种云提供商更丰富、更有用的集成,AWS 也不例外。如果你现在正在使用 ECS,希望这篇文章能给你一些关于如何使用 Octopus Deploy 进行部署的想法,利用 Octopus 擅长的所有东西。让我们知道您使用 Octopus 到 ECS 的成功(或失败)部署!

在 AWS - Octopus 部署中创建 EKS 集群

原文:https://octopus.com/blog/eks-cluster-aws

在本文中,您将学习如何在 Amazon Web Services (AWS)中建立一个弹性的 Kubernetes 服务(EKS)集群。

EKS 是一个托管容器服务,用于在云中或内部运行和扩展 Kubernetes。Kubernetes 提供了一种可扩展的分布式方法来管理工作负载。它通过容器化应用程序来做到这一点。容器确保了跨不同环境和云基础设施的可复制性。

您在本文中创建的集群将在我们的持续集成系列的后续文章中使用,以设置 web 应用程序并作为工作流的一部分。我们将在相关帖子可用时添加链接:

先决条件

要跟进这篇文章,你需要:

在 EKS 有两种方式来建立集群:

  • 命令行界面
  • 控制台

在此之前,您需要设置一些访问密钥和用户帐户。

初步设置

在 AWS 中,您需要配置访问策略。这些策略决定了哪种用户可以访问群集。

通常,遵循最小特权原则是有用的。这意味着您为用户提供了执行其角色所需的最小权限集。这通过不向用户授予超出他们需要的权限来支持云基础架构的安全性。

按照以下步骤设置您的访问密钥和用户帐户:

  • 进入 AWS 控制台,然后 IAM ,然后用户,然后添加用户
  • 给用户一个名字,并勾选访问键-编程访问
  • 点击下一个

New user

  • 选择直接附加现有策略,然后创建策略

IAM 策略允许您从命令行创建 EKS 集群。此策略中的操作是 eksctl 要求的最低策略。

  • 点击下一个的,并为策略命名
  • 点击添加策略

亚马逊然后向你展示访问密钥 ID秘密访问密钥。下载此文件供以后参考。

命令行界面

使用aws login登录 AWS 命令行。

运行aws configure

请提前输入您的访问密钥 ID 和秘密访问密钥。将区域设置为us-east-2并接受默认值。

现在您可以创建您的集群了。

eksctl create cluster \
--name my-cluster \
--region us-east-2 \
--fargate 

AWS Fargate 允许您运行容器,而无需管理服务器或 Amazon EC2 实例集群。这个概要文件提供了一个简单的方法来启动一个测试集群。测试集群配置:

kubectl get svc 

输出

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   25h 

运行以下命令查看集群节点:

kubectl get nodes -o wide 

输出

NAME                                                    STATUS   ROLES    AGE   VERSION              INTERNAL-IP       EXTERNAL-IP   OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
fargate-ip-192-168-129-76.us-east-2.compute.internal    Ready    <none>   25h   v1.20.7-eks-135321   192.168.129.76    <none>        Amazon Linux 2   4.14.243-185.433.amzn2.x86_64   containerd://1.4.6
fargate-ip-192-168-165-146.us-east-2.compute.internal   Ready    <none>   25h   v1.20.7-eks-135321   192.168.165.146   <none>        Amazon Linux 2   4.14.243-185.433.amzn2.x86_64   containerd://1.4.6 

查看集群上运行的工作负载:

kubectl get pods --all-namespaces -o wide 

输出

NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE   IP                NODE                                                    NOMINATED NODE   READINESS GATES
kube-system   coredns-85f9f6cd8b-2n8wr   1/1     Running   0          25h   192.168.129.76    fargate-ip-192-168-129-76.us-east-2.compute.internal    <none>           <none>
kube-system   coredns-85f9f6cd8b-c4jfk   1/1     Running   0          25h   192.168.165.146   fargate-ip-192-168-165-146.us-east-2.compute.internal   <none>           <none> 

控制台界面

您也可以从 AWS 控制台创建集群。

转到 EKS ,然后添加集群,然后创建

EKS Console

  • 名称:为您的集群命名
  • Kubernetes 版本:选择最新的 Kubernetes 版本
  • 集群服务角色:重用从 CLI 集群创建的服务角色

接受所有其他默认设置来创建集群。

检查集群

通过转到 EKS ,然后转到集群,检查您的两个集群的状态。

EKS Two Clusters

既然您的集群是活动的,那么您可以对它们执行操作,比如部署应用程序或配置资源。对于本例,我们将删除它们。

删除集群

您可以通过运行以下命令,使用 CLI 删除群集。用您的值替换分类名称和区域。

eksctl delete cluster --name my-cluster --region us-east-2

您可以使用控制台删除集群,方法是选择集群,单击删除,并键入要删除的集群的名称。

您只能按照创建资源的方式删除资源。这意味着通过 CLI 创建的群集只能通过 CLI 删除。通过控制台创建的集群只能通过控制台删除。

T32

结论

在本文中,您将在 AWS 中设置 IAM 权限。您使用 CLI 和控制台创建、检查和删除了 EKS 集群。

AWS 上的 EKS 允许您在云中提供 Kubernetes 服务来部署和扩展工作负载。

您创建的集群可用于设置 web 应用程序,并作为后续帖子中工作流的一部分。我们将在相关帖子可用时添加链接:

阅读我们的持续集成系列的其余部分。

愉快的部署!

将联合用户帐户授予 EKS 集群- Octopus 部署

原文:https://octopus.com/blog/eks-federated-users

如果你在 AWS 上使用过弹性 Kubernetes 服务(EKS ),你很可能经历过权限的挫败感。如果您不是 EKS 集群的创建者,那么您在 AWS 控制台中看到的唯一信息就是集群的整体状态。

有许多关于添加额外用户帐户的教程;然而,并没有一个明确的演示向您展示如何添加一个联合用户帐户。

在本文中,我将带您了解如何向 EKS 集群授予一个联合用户帐户。

你会学到什么

在与 EKS 合作完成我们的样本实例后,我决定写这篇文章。我需要查看由项目创建的集群上的部署资源的详细信息。该项目使用一个操作手册来创建使用 AWS 账户的 EKS 集群。尽管对我们的 AWS 帐户有完全访问权限,但我看不到集群的详细信息。更复杂的是,我使用一个联邦用户帐户登录 AWS 控制台。在研究这个话题的时候,我找到了一些教程,但是没有一个涵盖了整个过程。

在本演练中,您将学习如何:

  • 使用不同的帐户创建 EKS 群集
  • 查找联合帐户的信息
  • 将联合帐户添加到集群中

创建 EKS 集群

向 EKS 集群授予联合用户帐户时,如果您不是 EKS 集群的创建者,就会出现问题。不同的用户需要首先创建集群。

在我的例子中,我创建了一个拥有足够权限来创建 EKS 集群的用户帐户。我在 Octopus 中添加了 AWS 帐户,并使用操作手册中的以下步骤创建了一个集群:

  • 创建 EKS 集群
  • 添加集群作为部署目标

我使用的是 Octopus Deploy runbook,但是创建 EKS 集群部分并不是 Octopus 特有的,因为它使用 AWS CLI 来创建集群。

创建 EKS 集群

该步骤使用 Octopus Deploy 中的运行 AWS CLI 脚本步骤来创建 EKS 集群。

CLI 需要以下变量来创建集群:

  • 群集 Name
  • 地区
  • 阿恩的角色
  • 前节点角色阿恩
  • 子网 id(我使用 2)
  • 安全组 ID

这篇文章假设你对 AWS 有足够的了解,知道这些值应该是什么。以下脚本使用 CLI 首先创建 EKS 集群,然后创建节点组。在这些活动完成之后,它将集群的 URL 端点存储在一个输出变量中,供后续步骤使用。

# Get variables
$clusterName = $OctopusParameters['AWS.Kubernetes.Cluster.Name']
$region = $OctopusParameters['AWS.Region.Name']
$eksRoleArn = $OctopusParameters['AWS.EKS.Role.Arn']
$nodeRoleArn = $OctopusParameters['AWS.Node.Role.Arn']
$subnet1Id = $OctopusParameters['AWS.Network.Subnet1.Id']
$subnet2Id = $OctopusParameters['AWS.Network.Subnet2.Id']
$securityGroupId = $OctopusParameters['AWS.Network.SecurityGroup.Id']

# Create EKS cluster
$eksCluster = aws eks create-cluster --name $clusterName --role-arn $eksRoleArn --resources-vpc-config subnetIds=$subnet1Id,$subnet2Id,securityGroupIds=$securityGroupId
$eksCluster = $eksCluster | ConvertFrom-JSON

Write-Host "Waiting for cluster to be done creating..."
while ($eksCluster.Cluster.Status -eq "CREATING")
{
    # Wait for cluster to be done creating
    $eksCluster = aws eks describe-cluster --name $clusterName
    $eksCluster = $eksCluster | ConvertFrom-JSON
}

Write-Host "Status of cluster: $($eksCluster.Cluster.Status)" 

Write-Host "Creating node group..."
aws eks create-nodegroup --cluster-name $clusterName --nodegroup-name "$clusterName-workers" --subnets $subnet1Id $subnet2Id --instance-types "t3.medium" --node-role $nodeRoleArn --remote-access ec2SshKey="<Your EC2 keypair>"

Set-OctopusVariable -name "EKSURL" -value $eksCluster.Cluster.Endpoint 

将集群添加为部署目标

该步骤将新创建的集群作为目标添加到 Octopus Deploy 中。如果您不使用 Octopus,可以转到下一节,查找联邦帐户的信息

此步骤在运行脚本步骤中使用New-octopuskubernetargetcmdlet 将新创建的 EKS 集群添加到 Octopus Deploy。$eksUrl变量从上一步的输出变量中检索它的值。

# Get the variables
$clusterName = $OctopusParameters['AWS.Kubernetes.Cluster.Name']
$region = $OctopusParameters['AWS.Region.Name']
$eksUrl = $OctopusParameters['Octopus.Action[Create EKS Cluster].Output.EKSURL']

# Add new Kubernetes cluster target
New-OctopusKubernetesTarget -Name "Samples-#{Octopus.Space.Name | Replace " "}-EKS" -clusterName $clusterName -octopusRoles "PetClinic,EKS" -octopusAccountIdOrName "#{AWS.Account.Name}" -namespace "default" -skipTlsVerification $true -clusterUrl $eksUrl 

查找联合帐户的信息

在 AWS 控制台中导航到集群,您会看到以下消息:

Your current user or role does not have access to Kubernetes objects on this EKS cluster. This may be due to the current user or role not having Kubernetes RBAC permissions to describe cluster resources or not having an entry in the cluster’s auth config map.

EKS cluster access denied

您可以看到群集处于活动和健康状态,但仅此而已。

解决方案是将您的用户帐户添加到aws-auth配置图中。然而,对于联合帐户,这个过程是不同的,因为它们不会出现在 AWS 控制台的 IAM 用户部分。

联合用户被映射到一个 AWS 角色,因此您需要确定您的帐户被映射到哪个角色。幸运的是,这可以通过点击 AWS 控制台右上角的您的帐户轻松完成。这将显示您的联合帐户映射到的角色。

在这种情况下,您可以看到我的帐户被映射到AWSReservedSSO_DeveloperAccess_645f9848983dec35角色。

AWS User Account

将联合帐户添加到集群中

在添加联邦用户之前,您必须首先获得aws-auth配置映射的当前值。这可以使用kubectl命令kubectl get configmap/aws-auth -n kube-system来完成。

为了更容易地处理他的文件,我将命令输出到 JSON,并将其写入文件。这让我可以将 JSON 反序列化为 PowerShell 对象。

# Get current aws-auth configmap
kubectl get configmap/aws-auth -n kube-system -o json | Set-Content aws-auth.json

# Load the JSON as an object
$jsonPayload = (Get-Content aws-auth.json | Out-String | ConvertFrom-Json) 

加载到$jsonPayload变量后,您可以通过导航到变量的.data.mapRoles属性来访问相关数据。

默认情况下,mapRoles的值应该如下所示:

尽管对象被从 JSON 反序列化为 PowerShell 对象,mapRoles部分仍然是 YAML。

- groups: 
  - system:bootstrappers 
  - system:nodes 
  rolearn: arn:aws:iam::<AWS account ID>:role/<Role used to create cluster>
  username: system:node:{{EC2PrivateDNSName}} 

要添加您的联合用户,请附加以下内容:

- rolearn: arn:aws:iam:<AWS account ID>:role/<role name>
  username: "{{SessionName}}"
  groups:
    - system:masters 

整个部分应该如下所示:

- rolearn: arn:aws:iam:<AWS account ID>:role/AWSReservedSSO_DeveloperAccess_645f9848983dec35
  username: "{{SessionName}}"
  groups:
    - system:masters
- groups: 
  - system:bootstrappers 
  - system:nodes 
  rolearn: arn:aws:iam::<AWS account ID>:role/<Role used to create cluster>
  username: system:node:{{EC2PrivateDNSName}} 

最后一步是用修改后的版本替换现有的aws-auth配置图。使用 kubectl CLI 的replace命令来完成此操作。因为我是在 Octopus Deploy runbook 中这样做的,所以我使用了下面的 PowerShell 来进行修改:

# Get current aws-auth configmap
kubectl get configmap/aws-auth -n kube-system -o json | Set-Content aws-auth.json

# Load the JSON as an object
$jsonPayload = (Get-Content aws-auth.json | Out-String | ConvertFrom-Json)

# Create federated users rolearn piece
$federatedUsers = 
@"
`n
- rolearn: arn:aws:iam::<AWS Account ID>:role/AWSReservedSSO_DeveloperAccess_645f9848983dec35
  username: "{{SessionName}}"
  groups:
    - system:masters
- groups:
    - system:bootstrappers 
    - system:nodes
  username: system:node:{{EC2PrivateDNSName}} 
  rolearn: arn:aws:iam::<Your AWS Account ID>:role/<Role used to create cluster>
"@

# Add federated users to maproles
$jsonPayload.data.mapRoles = $federatedUsers

# Write the new information to file
Set-Content aws-auth.json -Value ($jsonPayload | ConvertTo-Json -depth 10)

# Replace the config map
kubectl replace -n kube-system -f aws-auth.json 

添加了联合用户帐户后,您现在可以看到集群的详细信息:

EKS cluster resources

解决纷争

应用更新后,您可能会注意到Node group处于降级状态:

EKS node group degraded

如果您注意到这一点,请检查节点组的健康问题。如果问题类型是带有描述The aws-auth ConfigMap in your cluster is invalid.拒绝访问,请确保将联合账户 YAML 的{{SessionName}}部分用双引号括起来:

- rolearn: arn:aws:iam::<Your AWS Account ID>:role/AWSReservedSSO_DeveloperAccess_645f9848983dec35
  username: "{{SessionName}}"
  groups:
    - system:masters 

EKS node group health

更新可能需要大约 10 分钟,但健康问题应该会解决。

结论

我花了大量时间搜索和收集将联合用户帐户添加到 EKS 集群所需的信息。解决{{SessionName}}需要用双引号括起来的问题特别耗时。我希望这篇文章通过引导你完成这个过程来节省你的时间。

愉快的部署!

Selenium 系列:通过电子邮件发送结果- Octopus 部署

原文:https://octopus.com/blog/selenium/34-emailing-the-results/emailing-the-results

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

我们现在能够从转发给 AWS Lambda 的 HTTP POST 请求中运行 Gherkin 特性文件,但是由于 API Gateway 和 Lambda 请求的时间限制不同,我们被迫以异步方式运行测试。这意味着我们最初的 HTTP 请求不再接收测试的输出,所以我们需要另一个返回测试结果的解决方案。

一个简单的解决办法是在测试结果出来后,通过电子邮件发给我们。通过发送电子邮件,runCucumber功能可以使用您已有的通信平台通知我们结果,让我们不必实施定制解决方案。

好消息是 AWS 提供了发送电子邮件的服务,我们可以很容易地将它绑定到我们的 Lambda 函数中。这项服务被称为简单电子邮件服务(SES)。

在我们可以使用 SES 之前,我们需要验证我们将显示的发送电子邮件的电子邮件地址。这个验证过程是亚马逊防止 SES 被用来发送垃圾邮件的方法之一。

要打开 SES 控制台,请单击Services链接,然后单击Simple Email Service链接。

C:\e1a769cc26af90404e05f1b3d2a36c04

点击左侧菜单中的Email Addresses链接,然后点击Verify a New Email Address按钮。

C:\2a4bbabe855a4bd884d5060233d087ce

输入您有权访问的电子邮件地址,然后单击Verify This Email Address按钮。

C:\c20b26b91a93a75d93e9379f556d456f

您将看到一个提示,告诉您已经发送了一封验证电子邮件。

C:\92c9eb3b38fe00a11ffb044cacd67087

邮件中会有一个你需要打开的链接。

C:\27233411237724a5000019a0818bfcbf

单击该链接会将您带到一个页面,告诉您该电子邮件地址已成功验证。

C:\efadf80f7a46f630e2d01999fa0b2310

回到 SES 控制台,我们现在可以看到电子邮件地址已经过验证。您可能需要单击“刷新”按钮,将状态更新为“已验证”。

C:\2c68f8f83fe44183ee293e5455e8eec1

既然我们已经配置了 SES,我们需要将它合并到我们的代码中。

pom.xml文件中,添加com.amazonaws:aws-java-sdk-ses依赖项。这个库包含我们用 ses 创建和发送电子邮件所需的类:

<project 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <!-- ... -->
  <dependencies>
    <!-- ... -->
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-ses</artifactId>
      <version>${aws.sdk.version}</version>
    </dependency>
    <!-- ... -->
  </dependencies>
</project> 

LambdaEntry类中,我们将创建一个名为sendEmail()的新方法:

package com.octopus;

import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder;
import com.amazonaws.services.simpleemail.model.*;
// ...
public class LambdaEntry {
  // ...
  private void sendEmail(final String to, final String results) {
    try {
      final AmazonSimpleEmailService client = AmazonSimpleEmailServiceClientBuilder.standard()
        .withRegion(Regions.US_EAST_1).build();

      final SendEmailRequest request = new SendEmailRequest()
        .withDestination(

      new Destination().withToAddresses(to))
        .withMessage(new Message()
          .withBody(new Body()
            .withText(new Content()
        .withCharset("UTF-8").withData(results)))
        .withSubject(new Content()
          .withCharset("UTF-8").withData("WebDriver Test Results")))
        .withSource("admin@matthewcasperson.com");

      client.sendEmail(request);
    } catch (final Exception ex) {
      System.out.println("The email was not sent. Error message: " + ex.getMessage());
    }
  }
} 

这个方法有两个参数:接收结果的电子邮件地址和结果本身:

private void sendEmail(final String to, final String results) { 

然后我们创建一个新的AmazonSimpleEmailService实例,配置为在美国东部 1 区工作:

try {
  final AmazonSimpleEmailService client = AmazonSimpleEmailServiceClientBuilder.standard()
    .withRegion(Regions.US_EAST_1).build(); 

SendEmailRequest类用于构造电子邮件本身。它有一个流畅的界面,允许我们定义目的地,邮件正文,电子邮件主题和发件人地址。请注意,此处使用的发件人地址必须是 ses 控制台中已验证的电子邮件地址之一:

final SendEmailRequest request = new SendEmailRequest()
  .withDestination(

new Destination().withToAddresses(to))
  .withMessage(new Message()
  .withBody(new Body()
    .withText(new Content()
    .withCharset("UTF-8").withData(results)))
  .withSubject(new Content()
    .withCharset("UTF-8").withData("WebDriver Test Results")))
  .withSource("admin@matthewcasperson.com"); 

最后一步是向客户端发送请求:

client.sendEmail(request); 

如果出现任何问题,我们会向控制台发送一条消息:

 } catch (final Exception ex) {
    System.out.println("The email was not sent. Error message: " + ex.getMessage());
  }
} 

要发送电子邮件,我们需要对runCucumber()方法做一些小的改动:

public String runCucumber(String feature) throws Throwable {

  File driverDirectory = null;
  File chromeDirectory = null;
  File outputFile = null;
  File txtOutputFile = null;
  File featureFile = null;

  try {
    driverDirectory = downloadChromeDriver();
    chromeDirectory = downloadChromeHeadless();
    outputFile = Files.createTempFile("output", ".json").toFile();
    txtOutputFile = Files.createTempFile("output", ".txt").toFile();
    featureFile = writeFeatureToFile(feature);

    cucumber.api.cli.Main.run(
      new String[]{
        "--monochrome",
        "--glue", "com.octopus.decoratorbase",
        "--format", "json:" + outputFile.toString(),
        "--format", "pretty:" + txtOutputFile.toString(),
        featureFile.getAbsolutePath()},
      Thread.currentThread().getContextClassLoader());

    sendEmail("admin@matthewcasperson.com", FileUtils.readFileToString(txtOutputFile, Charset.defaultCharset()));

    return FileUtils.readFileToString(outputFile, Charset.defaultCharset());

  } finally {
    FileUtils.deleteQuietly(driverDirectory);
    FileUtils.deleteQuietly(chromeDirectory);
    FileUtils.deleteQuietly(outputFile);
    FileUtils.deleteQuietly(txtOutputFile);
    FileUtils.deleteQuietly(featureFile);
  }
} 

我们创建一个变量来保存测试结果将被写入的临时文件:

File txtOutputFile = null; 

该变量由扩展名为.txt的临时文件初始化:

txtOutputFile = Files.createTempFile("output", ".txt").toFile(); 

然后,我们向cucumber.api.cli.Main.run()方法传递一个额外的参数,将测试结果保存为一个文本文件。Cucumber 中漂亮的输出格式会生成漂亮的纯文本日志文件:

"--format", "pretty:" + txtOutputFile.toString(), 

就在我们返回 JSON 响应之前,我们调用sendEmail()方法来发送纯文本结果。这样,即使不再有任何进程监听该方法的返回值,我们也可以得到结果:

sendEmail("admin@matthewcasperson.com", FileUtils.readFileToString(txtOutputFile, Charset.defaultCharset())); 

最后的改变是给runCucumber()函数使用 SES 发送邮件的权限。

身份和访问管理(IAM)服务为 AWS 中运行的代码和服务提供安全性。我们在前面创建用于无服务器应用程序的访问和密钥时看到了这个服务。

除了管理用户,IAM 还管理授予其他 AWS 服务的权限,包括 Lambda。在我们的例子中,我们需要授予 Lambda 函数用 SES 发送电子邮件的能力。我们通过在serverless.yml文件的 provider 部分下添加一个iamRoleStatements部分来授予这个许可。

在这里,我们将Effect设置为Allow,以表明我们正在授予执行某些动作的能力。Resource设置被设置为"*",这是该选项的唯一有效值。Action设置被设置为ses:SendEmail,这是与在 SES 中发送电子邮件相关的动作的名称。

provider:
  name: aws
  runtime: java8
  region: us-east-1
  iamRoleStatements:
    - Effect: Allow
      Resource: "*"
      Action:
        - ses:SendEmail 

用 Maven 重新编译代码,用 Serverless 重新部署。然后向runCucumber URL 发出另一个请求。

和以前一样,我们得到一个空响应,但是过了一段时间,传递给sendEmail()方法的电子邮件地址将收到一条消息,其中包含测试结果。

如果您没有看到该电子邮件,请检查您的垃圾邮件文件夹。Outlook 365 不断将这些电子邮件识别为垃圾邮件,其他提供商可能也会这样做。

C:\64fd79671c78abdc3a4f8f36373ecef3

我们现在有了一个完整的解决方案,可以从 HTTP 请求启动 Lambda 中的 WebDriver 测试,并通过电子邮件接收结果。无论我们启动 1 次测试还是 1000 次测试,我们在这里部署的基础设施都将无缝、可靠地扩展,以满足我们的要求。这就是使用 AWS 这样的云服务的威力。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

加密 Web.config 中的连接字符串- Octopus Deploy

原文:https://octopus.com/blog/encrypting-connection-strings

当指定连接字符串时,通常最好使用 Windows 身份验证,因为您不需要将密码放在 Web.config 文件中。然而,这并不是在每种情况下都有效,所以如果你必须在你的连接字符串中使用密码,那么最好加密它。Step 模板是增强 Octopus Deploy 本机功能的一个很好的方法。因此,为了使加密连接字符串部分更容易,我创建了一个新模板来完成这项工作。

可以抢新的 IIS 网站——加密网页。Octopus 部署库中的配置连接字符串。在幕后,该模板使用了aspnet_regiis工具来加密 Web.Config。将该步骤添加到您的部署流程将会带您进入配置的步骤详细信息页面。

Configure Encrypt Connection Strings

这个模板有一个名为 Website directory 的参数,通常在包部署步骤中设置为InstallationDirectoryPath。请注意,您必须将包部署步骤的名称指定为变量的一部分。

#{Octopus.Action[MyDeployPackageStep].Output.InstallationDirectoryPath} 

此步骤设计为在将包部署到 web 服务器后运行。不幸的是,这意味着如果您使用IIS web site and application pool特性,会有一个微小的窗口,其中 Web.config 不会被加密。为了绝对安全,最好在将 IIS 重新打印到新网站之前应用加密。要解决此问题,请关闭该功能并使用自定义 PowerShell 脚本来更新 IIS。

将该步骤添加到部署过程之后,您的下一个部署将有一个精心加密的连接字符串部分。

八达通部署 2.0 中的加密-八达通部署

原文:https://octopus.com/blog/encryption-in-2.0

我们在 UserVoice 上有一些相关的建议,我想在 Octopus 2.0 中提出来:

我想用这篇博文来分享我们将如何解决这些需求。Octopus 2.0 将支持许多与加密相关的不同场景。

  1. 加密数据库
    Octopus 中的 RavenDB 数据库需要加密,以允许客户“勾选”Octopus,用于需要在磁盘上加密任何配置信息的环境。例如,PCI-DSS 环境中的客户。
  2. 安全/敏感变量
    在 Octopus UI 中创建的一些变量可能也需要加密。例如,客户可能会创建一个变量来表示连接到数据库时使用的管理员密码。这超越了磁盘加密——这些值不应该由 REST API 返回,也不应该出现在活动日志中。它们应该是“只写”的。
  3. 敏感内部 Octopus 设置的存储
    例如,用于触手 X.509 证书的私钥目前存储在注册表中,使用 Base64 编码。这些值也应该加密。

为了实现这一点,我们将依靠两级加密。AES128 将使用“主密钥”来对称加密值。主密钥本身将使用 DPAPI 进行非对称加密。

万能钥匙

当 Octopus 第一次启动时,它会生成一个随机字符串,用作主密钥。这个主密钥将使用 DPAPI 加密,并存储在 Octopus 配置文件中。

每当你开启八达通管理工具时,八达通会提示你备份你的主密码。直到你点击类似于“我已经备份了我的主密钥”的东西,每次你打开管理工具时,你将继续得到提示。要备份您的密钥,我们只需将主密钥公开为未加密密钥的 Base64 编码表示。你可以把它打印出来或者粘贴到记事本中,然后保存在你喜欢的任何地方。

Backing up your master key

加密数据库

RavenDB 附带了一个包,可以对数据库进行加密。当我们在嵌入式模式下运行 Raven 时,我们将自动启用这个包,并使用我们的主密钥作为 Raven 加密包的密钥。这确保了所有文档和索引都被加密。由于在创建 Octopus 数据库时需要启用这个包(以后不能启用),所以这不会是一个选择加入/选择退出特性。它将被开箱。

在客户使用外部 RavenDB 数据库的场景中,将由他们来启用这个包并管理他们自己的密钥。

但是,以下内容将不会包含在 Raven 的加密包中:

  • 渡鸦附件
  • 日志文件

对于附件,我们将使用 AES128 和我们的主密钥自己加密和解密。然而,我们不会加密日志文件。以纯文本格式存储日志对于调试有很大的价值,而且因为我们会防止敏感变量出现在日志中(见下文),所以没有理由加密它们。

安全/敏感变量

当你创建一个变量时,你可以勾选一个使它“安全”的框。安全变量一旦被写入就不能被读取;它在 UI 中总是显示为'*** 【T6]',并且该值不能从 REST API 中获得。然而,当发送到触手时,它将作为一个变量可用。*

这些安全变量的值将使用主密钥和 AES128 加密存储。没有被标记为安全的变量只是将它们的值存储为纯文本。

在处理任何活动日志时,我们将从 TeamCity 处理密码参数的方式中获得灵感:任何安全变量的值都将被自动屏蔽。例如,如果用户不小心编写了这样的 Deploy.ps1 脚本:

Write-Host "Password is $MyPassword" 

Octopus 会看到安全值出现在日志中,并在日志条目通过网络发送或保存之前,自动将其替换为“ ******* ”。在用户界面中,您将看到:

Password is *************** 

这将通过使用string.Replace()来完成。当然,这确实意味着用户可以编写一个脚本来生成一个随机值,然后查找' ******* '来找出哪个是密码,如果密码只是简单的“hello”,则掩码可能会出现在错误的位置,从而暴露自己,但总的来说,该功能是一个额外的保护层,而不是 100%的防呆措施。

备份和恢复

目前,备份是通过 RavenDB 的 Smuggler API 使用文档的“导入”和“导出”来执行的。它生成的文件只是一个 GZIP 压缩的 JSON 文档列表。

我们的备份文件也需要加密,所以我们将从 smuggler 那里获取备份文件,添加活动日志和任何其他需要备份的文件,并将它们全部压缩到一个System.IO.Packaging包中。然后我们将使用主密钥对其进行加密。

当您从备份中恢复时,您需要输入主密钥,该密钥应该在设置 Octopus 时已经备份/打印/保存。然后,我们将在您的配置文件中设置主密钥,并执行恢复。

摘要

我们对这些问题的解决方案将涉及几个方面。我们将通过 Raven 包自动加密 Raven 中的所有文档。我们还将允许变量被标记为“安全的”,这将影响它们在 UI 中的显示方式。

我们所有的加密都将依赖于 AES128,使用随机生成的主密钥,主密钥本身将使用 DPAPI 存储,因此如果攻击者设法查看您的配置文件,他们仍然无法读取密钥,除非他们在您的 Octopus 服务器上运行代码。这一变化的唯一缺点是需要备份您的主密钥,我们将尽最大努力创造良好的用户体验。

这项功能目前正处于规划阶段,所以如果你的环境中这些功能很重要,我很乐意在下面的评论中得到你的反馈。

使用 Cypress - Octopus Deploy 进行端到端测试

原文:https://octopus.com/blog/end-to-end-testing-with-cypress

Professor Octopus testing

将测试代码作为开发过程的一部分的想法几乎已经被普遍接受。单元测试现在是大多数复杂代码库的共同特征。

然而,测试并没有随着单元测试而停止。描述应用程序生命周期中执行的测试模式的典型例子是测试金字塔(尽管有许多替代方案,如测试蜂巢),它描述了某些策略,如端到端测试,通常需要应用程序的实时运行实例来执行测试。

端到端测试的一个例子是通过像 Cypress 这样的工具,它与网页的交互方式与人类非常相似。这些测试必然要求 web 应用程序正在运行,这使得它们成为在测试环境中部署和运行 web 应用程序之后,在部署过程的最后阶段中包含的理想候选。

在这篇博客文章中,我们来看看在 Octopus 部署期间运行 Cypress 的一些实际问题。我还提出了一个解决方案,允许 Cypress 测试在最常见的场景中运行。

在您的部署过程中包括 Cypress

要解决的第一个也是最紧迫的问题是让 Cypress 进入您的部署管道。

也许实现这一点最明显的方法是在虚拟机或物理机上安装 Cypress 和 web 浏览器。有许多像 Ansible、Puppet 和 Chef 这样的工具可以以可重复的方式自动化这个过程。

直接安装工具的缺点是,当您在托管的 Octopus 实例中使用动态工作器时,没有简单的方法做到这一点,并且在 Kubernetes 这样的平台中,没有 VM 的概念供您预先配置。

一个更通用的解决方案是从 Docker 容器运行 Cypress。这很方便,因为 Cypress 团队已经完成了在其 Docker 映像中配置所有必需软件的艰苦工作。您还可以通过运行不同的 Docker 镜像版本,随心所欲地切换浏览器版本。

参考 Cypress 测试

我们需要解决的下一个问题是如何在部署中包含单独的 Cypress 测试。

将测试放入定制的 Docker 映像中是可能的,如果您的测试没有太大的变化,这可能是一个非常合理的解决方案。

然而,我怀疑大多数投资于端到端测试的团队会希望继续快速更新他们的测试脚本,而没有将它们包含在新的 Docker 映像中的负担。拥有一个通用的 Cypress Docker 映像来执行随机的测试脚本不是很好吗?

直接运行 Docker 的时候,这个相对容易。您只需将包含您的测试脚本的本地目录挂载到通用的 Cypress Docker 映像中。Cypress 文档为此提供了一个例子,它将当前目录挂载为 Docker 容器中的e2e目录:

docker run -it -v $PWD:/e2e -w /e2e cypress/included:6.4.0 

不幸的是,Kubernetes 不支持这种卷安装。您可以将配置图的内容作为文件挂载,但是该选项不支持目录结构。当用 Cypress 进行测试时,这是一个明显的限制,因为 Cypress 目录结构包括许多子目录

Octopus 通过 worker containers 提供了一个解决方案,worker containers 在一个由适当配置的 Docker 映像生成的容器内执行一个部署步骤。Octopus 步骤然后可以下载并解压缩一个包,并运行一个定制脚本。

在我们的例子中,我们将基于 Cypress 映像创建一个定制的(但仍然是通用的)Docker 映像,Octopus 可以在其中执行,以提取包含我们的测试脚本的包并执行 Cypress。

我们的 Docker 形象由以下Dockerfile组成:

FROM cypress/included:6.4.0
RUN apt-get update; apt-get install -y libicu-dev
RUN npm install -g inline-assets
ENTRYPOINT [] 

这个Dockerfile,是以柏树形象cypress/included:6.4.0为原型,安装libicu-dev(是需要的。NET Core applications)并安装 inline-assets 工具来处理 Cypress HTML 报告(稍后将详细介绍)并清除ENTRYPOINT以便 Octopus 可以覆盖运行图像时使用的命令。

这个 Docker 映像可以用下面的命令构建和发布,用您自己的 Docker Hub 用户名替换dockerhubusername:

docker build . -t dockerhubusername/workerimage
docker push dockerhubusername/workerimage 

在我的例子中,我创建了一个名为mcasperson/workerimage的图像。我们现在可以使用这个图像来创建 Octopus 执行容器。

创建样本 Cypress 测试

对于这个例子,我们将创建一个简单的介绍性 Cypress 测试,它不执行实际的工作,但是允许我们模拟运行端到端测试的过程。这个样本测试的代码可以在 GitHub 上找到。

我们从文件package.json开始,文件安装 Mochawesome reporter 来生成 HTML 报告:

{
  "name": "cypress-test",
  "version": "0.0.1",
  "description": "A simple cypress test",
  "dependencies": {
    "mochawesome-merge": "^4.2.0",
    "mochawesome": "^6.2.1",
    "mocha": "^8.2.1"
  }
} 

接下来我们有了cypress.json文件,它配置了我们测试的基本细节。请注意,我们已经启用了<!-- --> HTML 报告:

{
  "baseUrl": "https://google.com",
  "reporter": "mochawesome",
  "reporterOptions": {
    "charts": true,
    "overwrite": false,
    "html": true,
    "json": false,
    "reportDir": "."
  }
} 

最后,我们在sample_spec.js文件中有测试本身。这个测试总是通过,不与任何网页交互,但它证明了 Cypress 正在按预期运行:

describe('My First Test', () => {
  it('Does not do much!', () => {
    expect(true).to.equal(true)
  })
}) 

在运行npm install下载 Mocha 报告库之后,这些文件被打包成一个 ZIP 文件,我们可以将它上传到 Octopus 的内置提要中。为了方便起见, GitHub 发布页面已经预先打包了 ZIP 文件,可以随时使用。

下面是上传到 Octopus 内置提要的 Cypress 测试:

Cypress test package

运行测试

我们现在准备运行测试,作为我们部署的一部分。在运行脚本步骤中,我们将脚本配置为在容器映像部分下的 worker 映像中执行:

Worker image

接下来,我们运行以下 Bash 脚本:

cd cypresstest
cypress run > output.txt
RESULT=$?
inline-assets mochawesome.html selfcontained.html
new_octopusartifact "${PWD}/selfcontained.html" "selfcontained.html"
exit ${RESULT} 

第一行输入我们的 Cypress 测试包将被提取并运行的目录cypress。输出被定向到一个名为output.txt的文件:

cd cypresstest
cypress run > output.txt 

然后,我们捕获 Cypress 的退出代码,这将决定该步骤是成功还是失败:

RESULT=$? 

为了能够在 Octopus 中方便地查看测试结果,我们需要将构成 HTML 报告的所有单个文件(HTML、CSS 和脚本文件)捆绑成一个单独的、自包含的 HTML 文件。这就是我们在工人映像中安装的inline-assets工具的用武之地。它将读取报告 HTML 文件,内联所有外部资源,并创建一个名为selfcontained.html的自包含 HTML 文件:

inline-assets mochawesome.html selfcontained.html 

我们将报告文件捕获为 Octopus 工件:

new_octopusartifact "${PWD}/selfcontained.html" "selfcontained.html" 

然后,我们脚本的退出代码被设置为 Cypress 的退出代码:

exit ${RESULT} 

最后,我们配置下载和提取包含 Cypress 脚本的包的步骤:

当这个脚本运行时,我们自包含的 HTML 报告文件作为工件被捕获,并作为链接在任务摘要中公开:

我们可以直接在浏览器中打开此报告:

这样,我们现在就有了一种方法,可以在由通用的共享 Docker 映像创建的容器中运行定制的 Cypress 测试。为了更新测试,我们只需上传一个新的包到 Octopus,它们将包含在我们的下一个部署中。

在 Kubernetes 测试

我们选择使用 worker 容器的原因之一是它允许我们在 Kubernetes 集群中运行相同的测试。为了验证这一点,我们需要在 Kubernetes 集群中运行一个 Octopus worker。以下部署启动了 Kubernetes 集群中的一个触手:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: octopus-worker
  labels:
    app: tentacle
spec:
  selector:
    matchLabels:
      octopusexport: OctopusExport
  revisionHistoryLimit: 1
  replicas: 1
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: tentacle
        octopusexport: OctopusExport
    spec:
      containers:
        - name: worker
          image: index.docker.io/octopusdeploy/tentacle
          env:
            - name: Space
              value: Test
            - name: ACCEPT_EULA
              value: 'Y'
            - name: TargetWorkerPool
              value: Testing
            - name: ServerUrl
              value: 'https://mattc.octopus.app'
            - name: ServerApiKey
              value: API-xxx
            - name: ServerPort
              value: '10943'
          securityContext:
            privileged: true 

要在您自己的集群中运行这个 worker,请确保更改ServerUrlServerApiKeyTargetWorkerPoolSpace环境变量,以匹配您的服务器配置。

重要的是,这个部署创建的 pod 具有设置为trueprivileged标志。这是支持 Docker-in-Docker 所必需的,它在 Linux 触手映像中启用。Docker-in-Docker 允许我们的触手以与 VM 上的触手相同的方式执行工作容器。

当这个部署应用到集群时,您的 Octopus 实例将显示一个新的轮询工作器,如下所示:

Polling worker

此时,我们可以对这个基于 Kubernetes 的 worker 运行相同的脚本步骤,就像我们对一个基于 VM 的 worker 运行测试一样。因为 Octopus 负责传输测试脚本,所以我们巧妙地避开了必须在 pod 中装载脚本的限制。

结论

正如测试代码现在是常见的做法一样,通过端到端测试来验证部署越来越常见,以确保应用程序生命周期的这一阶段按预期工作。如今,我们被像 Cypress 这样的高质量测试平台宠坏了,通过使用定制的工人映像做一些基础工作,跨多个平台运行端到端测试变得很容易。

在这篇博文中,我们讨论了为什么 worker 容器对于运行测试如此有用,创建了一个定制的 worker 映像来执行我们的测试,查看了一个简单的 Cypress 测试脚本,并编写了一个简单的 bash 脚本来运行测试并收集结果。然后,我们看到了如何在 Kubernetes 集群中运行一个 worker,该集群被配置为使用 Docker-in-Docker 运行相同的测试。

结果是一个可重用的测试过程,允许 Cypress 测试脚本跨多个平台快速开发、部署和执行。通过一些小的调整,可以用端到端测试来验证您的 web 应用程序部署,确保在应用程序到达最终用户之前,应用程序生命周期的每个阶段都经过测试和验证。

愉快的部署!

Selenium 系列:特定环境处理- Octopus 部署

原文:https://octopus.com/blog/selenium/19-environment-specific-handling/environment-specific-handling

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

您可能已经注意到,在 BrowserStack 中对 Edge 浏览器运行测试时,窗口没有最大化。在最大化的窗口中运行测试通常是有意义的,以确保测试是在以一致的分辨率显示网页的情况下运行的,所以让我们添加一个新的方法来最大化窗口。

首先,我们将方法maximizeWindow()添加到AutomatedBrowser类中:

package com.octopus;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public interface AutomatedBrowser {

  // ...

  void maximizeWindow();
} 

然后我们将默认实现添加到AutomatedBrowserBase类中:

package com.octopus.decoratorbase;

import com.octopus.AutomatedBrowser;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class AutomatedBrowserBase implements AutomatedBrowser {

  // ...

  @Override
  public void maximizeWindow() {
    if (getAutomatedBrowser() != null) {
      getAutomatedBrowser().maximizeWindow();
    }
  }
} 

然后在WebDriverDecorator类中,我们添加代码来最大化浏览器窗口:

package com.octopus.decorators;

import com.octopus.AutomatedBrowser;
import com.octopus.decoratorbase.AutomatedBrowserBase;
import com.octopus.utils.SimpleBy;
import com.octopus.utils.impl.SimpleByImpl;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

public class WebDriverDecorator extends AutomatedBrowserBase {

  // ...

  @Override
  public void maximizeWindow() {
    webDriver.manage().window().maximize();
  }
} 

现在,在我们的测试中,我们可以在 URL 打开之前最大化窗口,调用automatedBrowser.maximizeWindow():

@Test
public void browserStackEdgeTest() {
    final AutomatedBrowser automatedBrowser =
            AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("BrowserStackEdge");

    final String formButtonLocator = "button_element";
    final String formTextBoxLocator = "text_element";
    final String formTextAreaLocator = "textarea_element";
    final String formDropDownListLocator = "[name=select_element]";
    final String formCheckboxLocator = "//*[@name=\"checkbox1_element\"]";

    final String messageLocator = "message";

    try {
        automatedBrowser.init();

        automatedBrowser.maximizeWindow();

        automatedBrowser.goTo("https://s3.amazonaws.com/webdriver-testing-website/form.html");

        automatedBrowser.clickElement(formButtonLocator);
        assertEquals("Button Clicked", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.populateElement(formTextBoxLocator, "test text");

        assertEquals("Text Input Changed", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.populateElement(formTextAreaLocator, "test text");

        assertEquals("Text Area Changed", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.selectOptionByTextFromSelect("Option 2.1", formDropDownListLocator);
        assertEquals("Select Changed", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.clickElement(formCheckboxLocator);
        assertEquals("Checkbox Changed", automatedBrowser.getTextFromElement(messageLocator));
    } finally {
        automatedBrowser.destroy();
    }
} 

该测试现在将运行,正如预期的那样,Edge 浏览器窗口将在 URL 打开之前最大化。

但是如果我们在手机浏览器上运行同样的测试会发生什么呢?让我们在一个测试中添加对automatedBrowser.maximizeWindow()的调用,该测试利用了当我们将字符串BrowserStackAndroid传递给工厂类时生成的AutomatedBrowser实例:

@Test
public void browserStackAndroidTest() {

    final AutomatedBrowser automatedBrowser =
            AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("BrowserStackAndroid");

    final String formButtonLocator = "button_element";
    final String formTextBoxLocator = "text_element";
    final String formTextAreaLocator = "textarea_element";
    final String formDropDownListLocator = "[name=select_element]";
    final String formCheckboxLocator =  "//*[@name=\"checkbox1_element\"]";
    final String messageLocator = "message";

    try {
        automatedBrowser.init();

        automatedBrowser.maximizeWindow();

        automatedBrowser.goTo("https://s3.amazonaws.com/webdriver-tests/form.html");

        automatedBrowser.clickElement(formButtonLocator);
        assertEquals("Button Clicked", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.populateElement(formTextBoxLocator, "test text");
        assertEquals("Text Input Changed", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.populateElement(formTextAreaLocator, "test text");
        assertEquals("Text Area Changed", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.selectOptionByTextFromSelect("Option 2.1", formDropDownListLocator);
        assertEquals("Select Changed", automatedBrowser.getTextFromElement(messageLocator));

        automatedBrowser.clickElement(formCheckboxLocator);
        assertEquals("Checkbox Changed", automatedBrowser.getTextFromElement(messageLocator));
    } finally {
        automatedBrowser.destroy();
    }
} 

这一次我们得到一个异常:

org.openqa.selenium.WebDriverException: Appium error: unknown error:
operation is unsupported on Android.

May 04, 2018 3:17:00 PM org.openqa.selenium.remote.ProtocolHandshake
createSession

INFO: Detected dialect: OSS

org.openqa.selenium.WebDriverException: Appium error: unknown error:
operation is unsupported on Android

(Session info: chrome=63.0.3239.111)

(Driver info: chromedriver=2.35.528139
(47ead77cb35ad2a9a83248b292151462a66cd881),platform=Linux
3.19.8-100.fc20.x86_64 x86_64) (WARNING: The server did not provide
any stacktrace information)

Command duration or timeout: 0 milliseconds

Build info: version: '3.11.0', revision: 'e59cfb3', time:
'2018-03-11T20:26:55.152Z'

System info: host: 'Christinas-MBP', ip: '192.168.1.84', os.name:
'Mac OS X', os.arch: 'x86_64', os.version: '10.13.4',
java.version: '10'

Driver info: org.openqa.selenium.remote.RemoteWebDriver

Capabilities {browserName: chrome, chromeOptions: {args: [test-type,
--proxy-server=http://65.74..., --disable-translate]},
databaseEnabled: false, deviceManufacturer: samsung, deviceModel:
SM-N950F, deviceName: ce051715d1a6a708017e, deviceScreenSize: 1440x2960,
deviceUDID: ce051715d1a6a708017e, enablePerformanceLogging: false,
javascriptEnabled: true, locationContextEnabled: false, loggingPrefs:
org.openqa.selenium.logging..., networkConnectionEnabled: true,
newCommandTimeout: 300, platform: LINUX, platformName: LINUX,
platformVersion: 7.1.1, realMobile: true, systemPort: 8203,
takesScreenshot: true, udid: ce051715d1a6a708017e, warnings: {},
webStorageEnabled: false}

Session ID: 1a34a4609f63d6bc8749bd3a09f5001ea5a93dd7 

C:\f22aeb0db211bb4354f00a062e71f0ea

这个例外是有意义的,因为移动浏览器没有可调整大小的窗口的概念。它们总是全屏显示,因此尝试修改窗口大小是无效的。

不过,这确实给我们留下了一个问题。理想情况下,我们希望在任何浏览器上运行我们的测试代码。尽管在这些文章中,我们一直在创建新的测试方法来演示新的浏览器,但在实践中,最好有一个针对不同浏览器多次调用的单一测试方法。运行单一的测试方法减少了重复代码的数量,使得测试更容易维护。

我们可以尝试在测试中检测运行测试的设备,并在一个if语句中包装调用以最大化窗口。下面的代码提取了设备制造商的名称,如果不是samsung,我们假设测试正在桌面设备上运行,并调用automatedBrowser.maximizeWindow():

String manufacturer = ((RemoteWebDriver) automatedBrowser.getWebDriver()).getCapabilities().getCapability("deviceManufacturer").toString();

if (!manufacturer.equalsIgnoreCase("samsung")) {
  automatedBrowser.maximizeWindow();
} 

这种解决方案是可行的,但不是很优雅。只有当我们测试的唯一移动设备是由三星制造的时候,代码才起作用,这意味着我们测试的每个新设备都需要新的代码来检查它是否是移动设备。它还用大量代码扰乱了我们的测试,分散了我们真正感兴趣的交互的注意力。

一个更好的解决方案是覆盖BrowserStackAndroidDecorator装饰类中的maximizeWindow()方法:

package com.octopus.decorators;

import com.octopus.AutomatedBrowser;
import com.octopus.decoratorbase.AutomatedBrowserBase;
import org.openqa.selenium.remote.DesiredCapabilities;

public class BrowserStackAndroidDecorator extends AutomatedBrowserBase {

  // ...

  @Override
  public void maximizeWindow() {
    // do nothing
  }
} 

这里我们添加了一个不做任何事情的maximizeWindow()方法的实现。我们知道,任何时候使用BrowserStackAndroidDecorator类,我们都必须使用移动浏览器,所以我们简单地忽略任何最大化窗口的请求。

这个解决方案意味着我们的测试代码在桌面浏览器或移动浏览器上运行时不需要修改。那些编写和维护测试的人不再需要考虑将运行最终测试的设备的种类,这使得测试更加健壮和易于维护。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

特定于环境的可变权限- Octopus 部署

原文:https://octopus.com/blog/environment-specific-variable-permission

今天我发布了另一个 Octopus Deploy 更新,主要包含了继 1.1 发布之后的小错误修复。然而,这个版本还包含了一个新的小特性——在环境级别控制可变的查看/编辑权限的能力。

例如,在下面的截图中,我已经拒绝了对哥伦比亚项目生产环境的变量查看/编辑权限:

Deny permission

现在,当有人试图查看或编辑该项目的变量时,他们会发现无法看到生产值:

This user can't see the production variable

编辑时,“生产”环境在“环境”下拉列表中不可用,并且他们不能对生产环境中已经存在的变量进行更改。

通常,您将为一个组(可能是“devops”)设置该权限,该组被允许查看/编辑生产变量,而拒绝其他任何人。

希望这一特性可以更容易地管理 Octopus 中的生产设置和密码,而不会让整个组织看到它们。

工人的执行容器- Octopus 部署

原文:https://octopus.com/blog/execution-containers

Execution Containers for Workers

现代部署依赖于工具。比如 AWS、Azure、Google 命令行、Terraform、kubectl、Helm、Java、NodeJS、。网,等等等等,等等

章鱼在历史上对这些采取了不一致的方法。有些与 Octopus 服务器捆绑在一起,并被推送到部署目标(在 Windows 上)。例如 Azure CLI、AWS CLI 和 Terraform。在其他情况下,Octopus 假设依赖项已经预先安装在目标上,例如 kubectl、Helm、Java。

这两种方法都不是完美的。

捆绑的依赖关系有许多缺点。它们总是过时的,用户经常需要最新版本的工具,但它们不能独立于 Octopus 服务器进行更新。没有办法固定捆绑的依赖项的版本,因此如果工具的发布者引入了突破性的更改,我们会发现自己无法在不潜在地导致用户部署过程失败的情况下更新它们(这是 Terraform 当前的一个问题)。

当然,通过而不是捆绑依赖关系,我们将痛苦推给了我们的用户。如果你必须安装几十个依赖项,那么让一台新机器旋转起来作为 Octopus Worker 是一件苦差事。并且管理各种项目的部署过程和工人之间的关系并不明显。

幸运的是,有一种技术非常适合这个场景:容器

容器允许捆绑依赖,并且(这是神奇的一点)在容器内执行命令。

CI 工具集中使用容器作为构建软件的执行环境并不是巧合。同样的能力也可以用于部署。

Octopus 2020.2 引入了在容器内运行部署动作的能力:

Action Container Image User Interface

这将可用于在 Worker 上执行的任何步骤(或者在 Octopus 服务器上,对于自托管实例)。

通常情况下,Octopus 服务器通过触手服务将 Calamari 可执行文件发送给工人,在工人机器上直接运行。

Worker Architecture without action containers

当一个步骤被配置为使用执行容器时,Octopus 服务器仍然会发送 Calamari,但是它不是直接在工作机上运行,而是在指定的容器内执行(使用 docker exec )。

Worker Architecture with action containers

您可以使用任何 Docker 映像,但为了帮助提供一个愉快的路径,我们已经将一些映像发布到 Docker hub(octopus deploy/worker-tools)上的存储库中,其中包含许多最常用的部署工具,我们将定期发布包含工具的最新版本的更新。如果您的部署过程需要不在octopusdeploy/worker-tools映像中的工具,或者您喜欢较小的映像,那么您可以使用另一个映像,甚至构建您自己的映像。

值得注意的是,当您将部署操作配置为在容器内部执行时,指定的映像会被以不同于正在部署的包的方式处理。动作图像是在部署过程中完全指定的(包括标签),不像已部署的包在创建发布时选择它们的版本。这很重要,因为它允许部署过程的维护者控制映像的更新时间,并且任何创建项目发布的人都不需要这些映像的任何知识。

由于 Octopus 继续支持部署多种类型的应用程序(。NET,Java,NodeJS 等。)到很多云(自托管、AWS、Azure 等)的很多平台(Windows、Linux、PaaS)。),我们希望这将成为驯服部署工具依赖的有力武器。

在 Octopus 2020.2 中,执行容器作为早期访问预览版提供。章鱼云动态工作器目前不支持该功能;在不久的将来,我们将增加对此的支持。

Action Container Feature Flag

愉快的(集装箱化)部署!

扩展代理支持- Octopus 部署

原文:https://octopus.com/blog/expanding-proxy-support

这篇文章是我们 Octopus 3.4 博客系列的一部分。在我们的博客或我们的推特上关注它。

Octopus Deploy 3.4 已经发货!阅读博文今天就下载


当前状态

在 3.4 版本之前,Octopus 中的代理支持仅限于您的 Octopus 服务器或需要与其他服务器通信的触手。它仅限于在部署期间,如果您的触手需要在 Powershell 脚本中发出 web 请求,或者在 Azure 部署期间,当 Octopus 服务器连接到 Azure 时。Octopus 服务器和 Tentacles 之间的通信通过我们的通信库 Halibut 进行,它不知道代理。

Current state of proxy support 八达通中代理支持的当前状态

这是因为 Octopus 依赖于。NET 的System.Net.WebRequest,但是大比目鱼在TcpClient级别工作。

扩展代理支持

在 3.4 中,我们已经教会了比目鱼如何游过代理。这意味着您现在可以让轮询触角通过代理轮询服务器,让 Octopus 服务器通过代理连接到侦听触角。如果您的网络需要,您甚至可以为不同的触角使用不同的代理服务器。

这将结束我们的用户需要乞求他们的公司 IT 部门只为八达通提供防火墙津贴。(在轮询是解决方法的某些情况下,它还允许使用监听触角。)

Expanded proxy support3.4 即将到来——代理无处不在!

设置它

让我们快速浏览一下,在配置监听触手时,如何配置 Octopus 来使用代理。首先,您需要在 Octopus 的配置部分设置代理详细信息:

Creating a proxy

然后,您可以在触手的通信设置区域中通过这个代理告诉 Octopus 与侦听触手进行通信:

Tell Octopus to use a Proxy for a Tentacle

就是这样!现在,章鱼服务器和你的触手之间的所有通信都将通过这个代理。轮询触角有一点不同,因为轮询触角发起所有通信,我们需要在触角端设置代理。当您安装轮询触手时,您现在可以选择设置一个代理,触手将使用它进行轮询(它也将用于触手在设置期间进行的注册调用):

Setting up the polling tentacle proxy

所以你有它。更多的代理支持比你可以摇一棒。如果这是你认为对你的 Octopus 使用有用的东西,为什么不下载 3.4 测试版并试用一下,或者更多信息,请参见 Octopus proxy 支持页面

如何从 AKS - Octopus Deploy 中的 Windows Kubernetes 节点导出指标

原文:https://octopus.com/blog/export-metrics-from-windows-kubernetes-nodes-in-aks

在 Octopus,我们使用 Azure Kubernetes 服务( AKS )来管理一个 Kubernetes 集群,我们将其用于一系列内部工具,以及构建和测试工作负载。

为了有效地使用这种资源,我们需要监控它的使用量以及几个方面——CPU、内存、磁盘、网络带宽等等。

对于 Linux 节点,我们使用 Sumo Logic 的解决方案从 Kubernetes 收集指标,但它目前不支持从 Windows 收集指标。我们在集群中使用 Windows 节点,所以我们也需要一种方法来监控它们。

在本帖中,我将向你介绍我们的解决方案。如果您还需要 Windows 节点度量,我希望您可以使用这篇文章找到适合您的解决方案。

收集 Windows 主机上的指标

收集关于 Kubernetes (K8s)节点的指标对于 Linux 节点来说有一个既定的模式:

  • 在所有 Linux 节点上运行一组特权 pods,并使用 node_exporter 收集主机级指标
  • 给它们贴上普罗米修斯能自动刮除的标签

这在 Windows 上不起作用,因为 Windows 容器不能(目前是)被赋予特权——这意味着它们不能看到主机 VM 的“外部世界”。所以我们需要其他方法来窥视宿主。

基于 GitHub 用户 aidapsibr 的一个奇妙的解决方案,我们构建了一个非常接近 Linux 标准模式的方法。在我们的集群中工作的其他人现在可以理解监控管道是如何工作的,而不会有太多的困惑。

有三个组成部分:

  1. 一个虚拟机规模集扩展,它在创建时在每个实例上安装 windows 导出器 Windows 服务
  2. 一个反向代理容器,用于将节点上运行的 windows-exporter 公开为集群中的服务
  3. Prometheus 将一些 Windows 指标转发到 Sumo 逻辑管道的两个远程写入规则

虚拟机规模集扩展

aidapsibr 的解决方案使用 PowerShell 脚本安装该扩展一次,但是当我们使用 Terraform 和连续部署部署 AKS 集群时,我们需要一种方法来确保任何 Windows 节点池中的任何虚拟机最终都使用该扩展。幸运的是,Azure provider for Terraform已经有了一个 scale set 扩展资源,所以我们将下面的内容添加到我们的 terra form 文件中:

data "azurerm_virtual_machine_scale_set" "blue_bldwin" {
  depends_on = [
    azurerm_kubernetes_cluster_node_pool.blue_nautilus_buildwindows
  ]
  name                = "aksbldwin"
  resource_group_name = join("", ["MC_", var.environment, var.name, "_", var.environment, var.name, "aks_australiaeast"])
}
resource "azurerm_virtual_machine_scale_set_extension" "blue_windows_exporter" {
  depends_on = [
    data.azurerm_virtual_machine_scale_set.blue_bldwin
  ]
  name                         = "windows-exporter-dsc"
  virtual_machine_scale_set_id = data.azurerm_virtual_machine_scale_set.blue_bldwin.id
  publisher                    = "Microsoft.Powershell"
  type                         = "DSC"
  type_handler_version         = "2.80"
  # ensure that the AKS custom script extension has already run
  provision_after_extensions = ["vmssCSE"]
  auto_upgrade_minor_version = false
  settings = jsonencode({
    wmfVersion = "latest"
    configuration = {
      url      = var.vmss_metrics_extension_zip
      script   = "aks_setup"
      function = "Setup"
    }
    privacy = {
      dataEnabled = "Disable"
    }
  })
} 

我们单独建立一个 DSC。zip 文件,上面引用为var.vmss_metrics_extension_zip,并将其作为 GitHub 发布工件托管。这个。zip 文件包含 windows-exporter。msi 安装程序和 PowerShell DSC 模块文件,一旦 VM 启动并运行,扩展将调用该文件。该模块只安装。msi 安装程序。

部署完成后,我们从集群内部手动抓取 Windows 节点,并获得普罗米修斯格式的指标:

# curl 10.240.0.4:9100/metrics | grep windows_cpu_time
# HELP windows_cpu_time_total Time that processor spent in different modes (idle, user, system, ...)
# TYPE windows_cpu_time_total counter
windows_cpu_time_total{core="0,0",mode="dpc"} 486.953125
windows_cpu_time_total{core="0,0",mode="idle"} 19035.953125
windows_cpu_time_total{core="0,0",mode="interrupt"} 18.53125
windows_cpu_time_total{core="0,0",mode="privileged"} 2327.78125
windows_cpu_time_total{core="0,0",mode="user"} 5324.1875
... 

现在,我们需要一种方法来允许 Kubernetes 服务(和 Prometheus ServiceMonitors)发现和收集这些指标——相当于用于 Linux 节点的 DaemonSet。

反向代理

我们将 nginx(一个广泛使用的反向代理)打包成一个容器,带有一个入口点,该入口点接收一个环境变量并将其用作这个配置文件中的上游服务器:

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    upstream backendhostname {
            server PROXY_HOSTIP:PROXY_PORT;
    }
    server {
        listen       9100;
        server_name  localhost;
        location /health {
            return 200;
        }
        location /metrics {
            proxy_pass http://backendhostname/metrics;
            proxy_http_version 1.1; 
            proxy_set_header Upgrade $http_upgrade; 
            proxy_set_header Connection "upgrade"; 
        }
    }
} 

通过将 nginx 容器部署为具有以下定义的 DaemonSet,将PROXYHOSTIP设置为节点的内部 IP:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: # a useful name
  namespace: # your monitoring namespace
  labels:
    # labels that match any existing Prometheus ServiceMonitors
spec:
  selector:
    matchLabels:
      # labels that match any existing Prometheus ServiceMonitors
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        # labels that match any existing Prometheus ServiceMonitors
    spec:
      hostNetwork: false
      containers:
        - name: windows-metric-proxy
          image: # your docker container location
          imagePullPolicy: Always
          ports:
            - name: metrics
              containerPort: 9100
              protocol: TCP
          env:
            - name: PROXY_HOSTIP
              # this will get the current node's internal IP and forward metric scrapes to the windows-exporter service running on the node
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: PROXY_PORT
              value: '9100'
      securityContext:
        runAsNonRoot: false
      nodeSelector:
        kubernetes.io/os: windows 

如果您的标签与您现有的 Linux 节点度量管道的ServiceMonitor相匹配,Prometheus 将自动挑选这些标签进行清理。对于相扑逻辑,这意味着:

labels:
  app: prometheus-node-exporter
  release: collection 

现在,集群中的 Prometheus 实例正在收集指标,我们将它们发送到 Sumo Logic 进行长期保留。

转发到相扑逻辑

Sumo Logic 收集解决方案是一个舵图,它安装了收集 Kubernetes 监控数据所需的一切(不仅仅是指标,还有事件、日志和各种各样的东西)。我们升级图表是我们持续部署集群基础架构的一部分。

该图表允许我们指定额外的远程写入规则,告诉 Prometheus 将指标发送到该图表也安装的 fluentd 实例——因此我们在我们的values.yaml覆盖文件中添加了两个规则,以将这些新的 Windows 指标发送到与 Linux 相同的位置。这是必要的,因为 windows 导出器不使用与 Linux 导出器相同的度量名称。

prometheus:
  prometheusSpec:
    nodeSelector:
      agentpool: default
    # Add new remote writes (toward fluentd) for our windows metrics, which are unfortunately prefixed with windows_
    # We don't need to use new fluentd targets because these represent the same measurements as the existing ones.
    remoteWrite:
      - url: http://$(FLUENTD_METRICS_SVC).$(NAMESPACE).svc.cluster.local:9888/prometheus.metrics.container
        remoteTimeout: 5s
        writeRelabelConfigs:
          - action: keep
            # regex: job-name;metric-name-regex
            regex: node-exporter;(?:windows_container_cpu_usage_seconds_total|windows_container_memory_working_set_bytes|windows_container_fs_usage_bytes|windows_container_fs_limit_bytes|windows_container_cpu_cfs_throttled_seconds_total|windows_container_network_receive_bytes_total|windows_container_network_transmit_bytes_total)
            sourceLabels: [job, __name__]
      - url: http://$(FLUENTD_METRICS_SVC).$(NAMESPACE).svc.cluster.local:9888/prometheus.metrics.node
        remoteTimeout: 5s
        writeRelabelConfigs:
          - action: keep
            regex: node-exporter;(?:windows_cpu_time_total|windows_logical_disk_free_bytes|windows_logical_disk_size_bytes|windows_memory_.*|windows_net_bytes_received_total|windows_net_bytes_sent_total|windows_os_.*|windows_system_.*)
            sourceLabels: [job, __name__] 

注意:我们需要包含原始图表中所有其他的remoteWrite规则,因为如果我们只使用上面的值,它们就不再存在了。

结论

连接上面的一切使我们能够在一个地方监视(例如)整个集群的节点磁盘消耗。这使得试验工作负载的变化变得非常容易,因为我们不再有耗尽集群资源的风险。

A screenshot of the Sumo Logic monitoring dashboard showing Windows and Linux disk metrics on one graph

愉快的部署!

了解更多信息

在空间之间导出和导入项目- Octopus 部署

原文:https://octopus.com/blog/exporting-projects

octopus branded bento box filled with sushi

2021 Q2 Octopus Deploy 版本包括一个新功能,使项目可以导出,然后导入到另一个空间,让您可以更多地控制如何组织您的 Octopus 实例。这也允许您轻松地将内部部署的 Octopus 项目迁移到 Octopus Cloud。

在开发过程中,我们称之为项目便当,因为目标是为你的章鱼项目提供一个可移植的容器。

空间和章鱼云

Octopus 2019.1 引入了 Spaces ,一种对你的 Octopus 服务器进行分区的方式。

我们没有包括在空间之间移动项目的功能,因为这个功能的设计和构建很复杂。对此的需求一直很强烈,反馈是,如果无法将现有项目划分为新的空间,空间的价值就会降低。

我们的客户解决方案团队通过 Space Cloner 项目填补了这一空白,这是一个 PowerShell 脚本集合,用于帮助在空间之间进行复制。这在很多情况下都是有用的,但是也有局限性,因为它是通过 HTTP API 操作的,而不是现成的。

快进两年,人们还希望将项目从自托管的 Octopus 实例迁移到 Octopus Cloud

2021 年 Q2 发布版引入了导出和导入项目的能力,这是一个完全受支持的内置特性。

Project Export/Import menu

导出项目

您可以选择一个或多个项目进行导出,并且您需要提供一个密码来加密导出的数据,因为导出将包含敏感变量。

Export projects page

导出作为任务运行,并生成一个可以下载的 zip 文件。

导入项目

然后可以将 zip 文件导入到同一台 Octopus 服务器上的另一个空间、另一台服务器上,甚至是 Octopus 云实例上。

Import projects page

与导出类似,导入作为任务运行(大型导出可能需要一些时间)。

选定的项目及其所需的一切都包括在内:

  • 环境
  • 房客
  • 可变集合
  • 步骤模板
  • 帐目
  • 证书

注意不包括的内容也很重要:

  • 部署目标。这些将需要在目标空间中重新配置,因为需要显式配置 Tentacles 来信任另一个 Octopus 服务器。
  • 。自托管项目拥有数百千兆字节的包是很常见的。将这些包含在出口 zip 中是不切实际的。在许多情况下,不需要旧的包,构建服务器可以简单地指向新的 Octopus 服务器。对于需要包的情况,我们提供了一个示例脚本来演示通过 Octopus API 同步包。

何时使用项目导出/导入功能

项目导出/导入特性的第一次迭代主要是为项目的一次性导入/导出而设计的。这在下列情况下很有用:

  • 将项目从自托管实例移动到 Octopus Cloud。
  • 在同一实例的两个空间之间移动项目,例如将一个空间拆分为多个空间时。

这个迭代没有解决在空间之间重复移动项目的问题,例如测试升级或者将项目升级到一个安全的、隔离的 Octopus 实例。

这些场景在细微但重要的方面有所不同,通常需要不同的变量值、生命周期、租户等。,在实例之间进行维护。我们已经用这个特性为支持这些场景打下了基础,并希望在未来的版本中也能这样做。同时,迁移器实用程序仍然是一个有效的选项。

结论

项目导出/导入功能现在在 Octopus Cloud 实例中可用,并且是可从下载页面获得的 2021.1 版本的一部分。

该功能目前是一个早期版本,我们非常希望听到您的反馈。我们希望这能让项目从自托管迁移到 Octopus Cloud 变得简单而顺利。

观看网络研讨会

https://www.youtube.com/embed/Dm4vOwuo9GI

VIDEO

我们定期举办网络研讨会。请参见网络研讨会第页,了解过去的网络研讨会和即将举办的网络研讨会的详细信息。

愉快的部署!

硒系列:暴露小黄瓜步骤-章鱼部署

原文:https://octopus.com/blog/selenium/26-exposing-gherkin-steps/exposing-gherkin-steps

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

现在我们已经有了将AutomatedBrowserBase类与 Cucumber 集成的基础,是时候开始充实我们可以用来编写 WebDriver 测试的小黄瓜方言了。

在大多数测试中,打开浏览器后的下一步是打开一个 URL。我们可以通过注释goTo()方法来揭示这一点。

我们将使用正则表达式^I open the URL "([^\"]*)"$来捕获我们希望打开的 URL,并将其传递给url参数。这个正则表达式遵循我们熟悉的模式,即使用脱字符号和美元符号来匹配字符串的开头和结尾,一些必须进行字面匹配的纯文本,以及单组引号:

@And("^I open the URL \"([^\"]*)\"$")
@Override
public void goTo(String url) {
  // ...
} 

现在我们需要公开一些方法来与网页交互。这里我们已经注释了clickElementWithId()方法:

@And("^I click the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\"$")
@Override
public void clickElementWithId(String id) {
  // ...
} 

我们用来作为一个步骤公开这个方法的正则表达式包含一些新概念。

我们使用了一些字符类来匹配常见的字符类型。字符类是一个反斜杠,后跟一个定义类的字符。

\w字符类匹配任何单词字符,这意味着它匹配任何小写字符、任何大写字符和任何数字。

字符类匹配任何空白字符,比如空格或制表符。

您可以在 Oracle 文档中找到关于正则表达式字符类的更多信息。

在这个正则表达式中,我们还使用了一个非捕获组。非捕获组以(?:开始,以)结束。非捕获组很有用,因为它们定义了我们可以选择或重复的匹配,而当模式与字符串匹配时,不会被捕获为一个不同的组。这对于与 Cucumber 的集成非常重要,因为非捕获组不会作为参数传递给相关的方法。

模式\\w+(?:\\s+\\w+)*匹配由空格分隔的零个或多个单词。因此,以下字符串将全部匹配此模式:

  • 文字框
  • 下拉列表
  • 文本区域
  • 单选按钮
  • 检验盒

我们使用这种模式来允许以最自然的方式描述交互的元素。因为该模式使用非捕获组,Cucumber 不会将匹配的文本作为参数传递给该方法。

下表将正则表达式分解为各个部分:

模式 意义
^ 匹配字符串的开头
I click the 匹配文字字符串I click the
\\w+ 匹配一个或多个单词字符
(?: 启动非捕获组
\\s+ 匹配一个或多个空白字符
\\w+ 匹配一个或多个单词字符
)* 结束非捕获组,并进行零次或多次匹配
with the id \" 匹配文字字符串with the id "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" 匹配文字字符串"
` 模式
--- ---
^ 匹配字符串的开头
I click the 匹配文字字符串I click the
\\w+ 匹配一个或多个单词字符
(?: 启动非捕获组
\\s+ 匹配一个或多个空白字符
\\w+ 匹配一个或多个单词字符
)* 结束非捕获组,并进行零次或多次匹配
with the id \" 匹配文字字符串with the id "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" 匹配文字字符串"
匹配字符串的结尾

要了解匹配该正则表达式的步骤,请将其输入到http://regex-testdrive.com/en/dotest中,并测试以下示例:

  • 我单击 id 为“email”的文本框
  • 我单击 id 为“option-1”的复选框
  • 我点击 id 为“提交”的红色大按钮

C:\d117bb78328e97f7dab22e91ecfeae72

注意,只捕获了两个组:组 0 是整个字符串,组 1 是我们想要单击的元素的 ID。然后,Group 1 作为第一个参数传递给该方法。

接下来,我们公开实现显式等待的重载版本clickElementWithId()。这个方法有第二个名为waitTime的参数,它接受一个int:

@And("^I click the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
@Override
public void clickElementWithId(String id, int waitTime) {
  // ...
} 

这个方法的正则表达式与前一个相似,但是增加了一个捕获组来捕获等待元素可点击的时间。

这个正则表达式使用了一个新的字符类\d,它匹配任何数字。

下表将正则表达式分解为各个部分:

模式 意义
^ 匹配字符串的开头
I click the 匹配文字字符串I click the
\\w+ 匹配一个或多个单词字符
(?: 启动非捕获组
\\s+ 匹配一个或多个空白字符
\\w+ 匹配一个或多个单词字符
)* 结束非捕获组,并进行零次或多次匹配
with the id \" 匹配文字字符串with the id "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" waiting up to \" 匹配文字字符串" waiting up to "
( 启动捕获组
\\d+ 匹配一个或多个数字字符
) 结束捕获组
\" second 匹配文字字符串" second
s? 匹配零个或一个s字符
` 模式
--- ---
^ 匹配字符串的开头
I click the 匹配文字字符串I click the
\\w+ 匹配一个或多个单词字符
(?: 启动非捕获组
\\s+ 匹配一个或多个空白字符
\\w+ 匹配一个或多个单词字符
)* 结束非捕获组,并进行零次或多次匹配
with the id \" 匹配文字字符串with the id "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" waiting up to \" 匹配文字字符串" waiting up to "
( 启动捕获组
\\d+ 匹配一个或多个数字字符
) 结束捕获组
\" second 匹配文字字符串" second
s? 匹配零个或一个s字符
匹配字符串的结尾

要了解匹配该正则表达式的步骤,请将其输入到http://regex-testdrive.com/en/dotest中,并测试以下示例:

  • 我点击 id 为“email”的文本框,等待“1”秒
  • 我单击 id 为“option-1”的复选框,等待“10”秒
  • 我点击 id 为“提交”的红色大按钮,等待“5”秒

C:\4f279d1029084cec389ed1a693b43b4e

这个正则表达式有三个捕获组。同样,组 0 是整个字符串。与前面的正则表达式一样,group 1 是要单击的元素的 ID,Cucumber 将它作为第一个参数传递给方法。新的 group 2 捕获显式等待时间,Cucumber 将其作为第二个参数传递给该方法。

让我们看看我们将用来定义一个步骤的最复杂的正则表达式。selectOptionByTextFromSelectWithId()方法接受下拉列表中要选择的选项的名称、下拉列表的 ID 以及等待下拉列表可点击的时间。这个正则表达式有 3 个捕获组:

@And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
@Override
public void selectOptionByTextFromSelectWithId(String optionText, String id, int waitTime) {
// ...
} 

下表将正则表达式分解为各个部分:

模式 意义
^ 匹配字符串的开头
I select the option \" 匹配文字字符串I select the option "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" 从匹配的文字字符串" from the
\\w+ 匹配一个或多个单词字符
(?: 启动非捕获组
\\s+ 匹配一个或多个空白字符
\\w+ 匹配一个或多个单词字符
)* 结束非捕获组并匹配它零次或多次
with the id \" 匹配文字字符串with the id "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" waiting up to \" 匹配文字字符串" waiting up to "
( 启动捕获组
\\d+ 匹配一个或多个数字字符
) 结束捕获组
\" 第二个匹配文字字符串" second
s? 匹配零个或一个s字符实例
` 模式
--- ---
^ 匹配字符串的开头
I select the option \" 匹配文字字符串I select the option "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" 从匹配的文字字符串" from the
\\w+ 匹配一个或多个单词字符
(?: 启动非捕获组
\\s+ 匹配一个或多个空白字符
\\w+ 匹配一个或多个单词字符
)* 结束非捕获组并匹配它零次或多次
with the id \" 匹配文字字符串with the id "
( 启动捕获组
[^"]* 匹配除双引号以外的任何字符零次或多次
) 结束捕获组
\" waiting up to \" 匹配文字字符串" waiting up to "
( 启动捕获组
\\d+ 匹配一个或多个数字字符
) 结束捕获组
\" 第二个匹配文字字符串" second
s? 匹配零个或一个s字符实例
匹配字符串的结尾

要了解匹配该正则表达式的步骤,请将其输入到http://regex-testdrive.com/en/dotest中,并测试以下示例:

  • 我从 id 为“dropdownlist”的下拉列表中选择选项“Option One ”,等待“1”秒
  • 我从下拉列表中选择了选项“一个很长的选项名”, id 为“the-select-element ”,等待了“10”秒
  • 我从 id 为“alongid”的选项列表中选择“这也是一个长名字”选项,等待“5”秒

【T2 C:\98467ce11d951c72ec283e76b8d449e3

现在我们有 4 个捕获组。因为 group 0 总是整个字符串,所以第一个捕获组是要选择的选项的名称,第二个是下拉列表的 ID,第三个是等待元素可点击的时间。

正如您所看到的,正则表达式非常强大,我们只涉及了正则表达式匹配字符串的一小部分方法。然而,这些正则表达式展示了我们将方法映射到小黄瓜步骤时使用的所有技术。剩下的所有方法都将使用正则表达式,其中包含我们在前面的方法中演示的捕获组、非捕获组、字符类和特殊字符的某种组合。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

工人的执行容器:扩展 Octopus 工人-工具 Docker 映像- Octopus 部署

原文:https://octopus.com/blog/extending-octopus-execution-container

Extending the Octopus worker-tools Docker image

我们最近为工人发布了执行容器,我们认为有很多理由使用这个新功能。如果你不熟悉执行容器,Michael 在上面链接的文章中做了很好的介绍,你也可以看看的执行容器文档

在这篇文章中,我将看看如何扩展 Octopus worker-tool 图像,以及何时需要使用完全不同的图像。

Octopus 提供了一个 Docker 映像,它可以用作部署流程或 runbook 中某个步骤的执行容器。该映像已经包含了部署所需的大部分工具。但是,如果您需要一个没有包含在映像中的工具,或者您需要包含在映像中的某个工具的不同版本,该怎么办呢?

让我们看一个例子,除了 Octopus 提供的 Docker 图像之外,我们还需要一些东西。假设我有一个需要与 SQL Server 数据库交互的项目步骤,在worker-tool映像中没有包含 SQL 工具。我想使用 mssql-cli 。这是 Docker 的伟大之处之一;我们可以获取octopusdeploy/worker-tools图像并扩展它以包含附加工具。

这是我们的文档:

FROM octopusdeploy/worker-tools:1.0-ubuntu.18.04

ARG DEBIAN_FRONTEND=noninteractive

RUN  echo "deb [arch=amd64] https://packages.microsoft.com/debian/8/prod jessie main" | tee /etc/apt/sources.list.d/mssql-cli.list  && \
  apt-get -y update  && \
  apt-get -y install mssql-cli  && \
  # Install missing dependencies
  apt-get -y install -f 

这个文件基于带有标签1.0-ubuntu.18.04的镜像octopusdeploy/worker-tools,在包管理器apt-get -y update更新后,它安装带有apt-get -y install mssql-climssql-cli

在写出版本的基本 Octopus 步骤中使用它,我们可以看到mssql-cli在那里并且可用:

通过扩展octopusdeploy/worker-tools图像并添加几行代码,我们可以使用基础图像中所有可用的工具,并且拥有我们想要的额外工具。

选择特定版本的 PowerShell

Dockerfile 文件中指定的软件版本在构建时是固定的;当您使用 Docker 图像时,您不能更改它们。假设您需要一个更高版本的 PowerShell,因为需要修复一个对您的部署过程至关重要的错误。有几种方法可以实现这一点。

首先是将版本更新添加到我们的扩展 docker 文件中:

FROM octopusdeploy/worker-tools:1.0-ubuntu.18.04

ARG DEBIAN_FRONTEND=noninteractive
ARG Powershell_Version=7.0.2\*    # UPDATED Powershell Version

RUN  echo "deb [arch=amd64] https://packages.microsoft.com/debian/8/prod jessie main" | tee /etc/apt/sources.list.d/mssql-cli.list  && \
  apt-get -y update  && \
  apt-get -y install mssql-cli  && \
  # Install specific version of Powershell
  apt-get install -y powershell=${Powershell_Version} && \
  # Install missing dependencies
  apt-get -y install -f 

我已经将它添加到我的octo crock/extend-octo worker-SQL-CLIdocker file 中,并在推送到 Dockerhub 之前用新版本 1.0.1 标记了编译后的映像。现在图像有两个标记版本:

  • 1.0.0:这是通过添加mssql-cli来扩展octopusdeploy/worker-tools图像的第一个版本。
  • 1.0.1:该版本进一步扩展了镜像,设置了不同版本的 PowerShell。

通过使用标记为 1.0.1 的图像,我们可以看到不同版本的 PowerShell:

指定不同版本的另一种方法是从 Octopus Worker Tools Github 库中获取 Dockerfile 并使用它来创建您自己的 Dockerfile,将版本号ARG值设置为最适合您的部署过程的值:

FROM ubuntu:18.04

ARG Powershell_Version=7.0.2\*      # UPDATED Powershell Version
ARG Octopus_Cli_Version=7.3.2
ARG Octopus_Client_Version=8.4.0
ARG Azure_Cli_Version=2.4.0\*
ARG Azure_Powershell_Version=2.2.0
ARG Helm_Version=v3.0.2
ARG Node_Version=12.16.3\*
ARG Kubectl_Version=1.11.1-00
ARG Terraform_Version=0.12.24
ARG Eks_Cli_Version=0.18.0
ARG Ecs_Cli_Version=1.18.1
ARG Aws_Iam_Authenticator_Version=1.16.8
ARG Umoci_Version=0.4.5
... 

这样,您只需安装一次 PowerShell,并且不会增加映像的体积,如果您想要指定其他工具的不同版本,这一点尤其正确。

尺寸考虑

Octopus 映像涵盖了许多部署场景,作为执行容器的起始映像,它是一个不错的选择。让我们来看看它的大小:

很大,3.44 GB。这就是 Linux 的风格;Windows 图像更大,但是与某些图像相比,它并不是那么大,并且当您考虑到您不太可能在该图像上使用所有工具时,显然可以节省空间。

在撰写本文时,octopusdeploy/worker-tools图像上的内容如下:

  • 18.04 版的 PowerShell
  • Octopus CLI
  • 八达通客户端
  • AZ PowerShell 核心模块
  • Helm3
  • 。NET SDK 3.1
  • JDK
  • 常见 Java 工具
  • Azure CLI
  • NodeJS
  • 库贝特尔
  • 将(行星)地球化(以适合人类居住)
  • 谷歌云 CLI
  • python & groff
  • AWS CLI
  • EKS CLI
  • ECS CLI
  • AWS IAM 验证器
  • Istio CLI
  • Linkerd CLI
  • 不使用 Docker 守护程序处理 Docker 映像的工具
  • 编写脚本的常用实用程序

这太棒了!

您可以使用所有这些工具,而不必知道 Docker 文件是如何构造的,没有必要建立自己的 Docker 存储库,也没有必要在 Worker VM 上安装所有这些依赖项,您可以直接开始使用执行容器。

然而,我还没有看到一个单独的 Octopus 部署步骤与 AWS、GCP 和 Azure 的基础设施进行交互。Net、Java 和 NodeJS 软件。我在这里开玩笑,但是我你能明白我的观点,你不太可能同时需要这张图片上的所有东西。

为什么 Docker 图像大小是一个考虑因素?

在工作机从存储库中下载了容器映像之后,每当 Octopus 部署流程步骤需要它时,它就可以重用它;它不会重新下载它。当有一个新版本的图像,只有新的被下载,而不是整个图像。

当您开始使用多个工作器,尤其是动态工作器时,容器映像大小真正变得引人注目。如果为部署步骤获取了一台工作机,而该机器还没有从存储库中提取 Docker 映像,则映像越大,执行该步骤所需的时间就越长。

在撰写本文时,章鱼云动态工作人员没有安装 Docker。我们正在努力将 Docker 添加到动态机器映像中,所以它将很快可用!

把它剥开

让我们把事情往另一个方向发展。如果我有一个运行 Azure CLI bash 脚本的部署步骤会怎样?

这里的 Linux 容器所需的工具很少,基本上只有 Ubuntu 库和 Azure CLI。我还需要wgetapt-utilssoftware-properties-common,安装 Azure CLI 所需的基本实用程序。考虑到这一点,我可以将 docker 文件减少到很少几行:

FROM ubuntu:18.04

ARG DEBIAN_FRONTEND=noninteractive
ARG Azure_Cli_Version=2.4.0\*

# Install wget, apt-utils, and software-properties-common
RUN apt-get update && \ 
    apt-get install -y wget apt-utils && \
    apt-get install -y software-properties-common 

# Install the Azure CLI
RUN wget --quiet -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/microsoft.asc.gpg > /dev/null && \
    echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ bionic main" | tee /etc/apt/sources.list.d/azure-cli.list && \
    apt-get update && \
    apt-get install -y azure-cli=${Azure_Cli_Version}

# Tidy up        
RUN apt-get clean 

大小的减少是显著的,新创建的octocrock/minimumcli-az映像比 worker-tools 映像小 80%,只有 687 MB。:

使用这个容器,我们可以执行一个 Azure 脚本步骤,并从az version看到与使用octopusdeploy/worker-tools图像的步骤相同的结果:

结论

这是我第一次看到 Octopus 中的执行容器功能,我不得不说,我可以看到它的好处。更重要的是,它取消了服务器软件的配置,意味着您可以通过一种版本控制的方式来指定部署软件的依赖关系。当然,您可以使用脚本通过您选择的包管理器来安装所需的软件,但是通过使用容器,您消除了软件安装冲突的风险,可以使用多个版本的工具,并且保持一个具有最低安装要求的工作人员。这有可能减少您需要的工作机数量,从而降低成本。

octopusdeploy/worker-tools映像意味着您可以立即开始,其中包含部署所需的大多数工具。除此之外,如果您使用自己的映像,您可以进一步增加部署流程的灵活性,并有可能减少部署时间。

愉快的部署!

农民:用章鱼部署更简单的武器部署-章鱼部署

原文:https://octopus.com/blog/farmer-and-octopus-deploy

Farmer ARM deployments and Octopus

与 Azure 合作了一年左右,很明显可以看出为什么 ARM 模板会受欢迎。它们提供了一个声明性的模型,只需点击一个按钮就可以生成整个环境。

然而,如果像我一样,你曾经试图创作一个 ARM 模板文件,你可能会遇到我最大的抱怨之一;它们依赖于字符串,容易出现人为错误。当我在一个模板中有一个打字错误时,没有编译器来帮助我(而且已经有很多了!).

自 2012 年以来,我一直将 C#作为我的主要开发语言,然而,从那以后,它的功能对等物 F#变得越来越受欢迎。正如我最近发现的,它有一些有用的特性可以帮助我摆脱 ARM 模板的困境。F#特别擅长的一个领域是它内置的类型安全。

在这篇文章中,我将通过使用 Farmer 生成一个简单的 Azure WebApp ARM 模板来演示 F#中的类型安全,然后我将介绍如何通过 Octopus 使用它的部署功能将不同的 WebApp 直接部署到 Azure。

在这篇文章中

什么是农民?

农夫的作者:

Farmer 是一个开源软件,可以免费使用。NET 领域特定语言(DSL ),用于快速生成不复杂的 Azure 资源管理器(ARM)模板。

要使用 Farmer,您需要创建一个 Farmer 模板。这些是。NET 核心应用程序通过一个 NuGet 包引用 Farmer,它们定义你希望创建的 Azure 资源。

为什么需要农民?

与其重复已经存在的内容,我建议您阅读 Farmer 文档的第节的部分,以获得更多关于为 ARM 模板创建 DSL 的动机的详细信息。

对我来说,亮点是:

  • 它提供了一组可以用来创建 Azure 资源的类型,并且它消除了创建无效模板的机会,因为它们是强类型的。
  • 它可以以非常简洁的方式生成简单的 ARM 模板,并可选地部署它们。

创建农民模板

要创建 Farmer 模板,我们首先需要创建一个. NET 核心应用程序。您可以在您选择的 IDE 中这样做,或者如果您喜欢命令行,您可以使用dotnet new命令,传递您需要的应用程序类型的模板。

农民模板通常使用控制台应用程序,您可以使用dotnet new console命令创建一个:

dotnet new console -lang "F#" -f "netcoreapp3.1" -n "SimpleAzureWebApp" 

这会创建一个新的 F#。名为 SimpleAzureWebApp 的 NET Core 3.1 应用程序,使用我们提供的-n参数。

接下来,我们需要通过运行add package命令将 Farmer 添加到项目中:

dotnet add package Farmer 

现在我们有了依赖项,我们可以继续编辑在我们创建新的控制台应用程序时自动生成的Program.fs文件。

TL;博士

如果你想看完整的程序,直接跳到结尾或者查看源代码。如果你想了解更多细节,请继续阅读!

模板参数

为了使 Farmer 模板更加灵活,我们将在应用程序中添加一些参数。这将允许我们提供不同的价值,Farmer 将基于这些价值在 Azure 中创建我们的资源。

我们需要的前三个与 Azure 认证相关。这些值可以通过创建一个 Azure 服务主体来获得。

  • AppID :用于服务主体的应用标识符。
  • Secret :服务主体使用的密码。
  • TenantID :用于服务主体的 ClientID。

安全凭证:
将你用来登录 Azure 的凭证存储在一个安全的位置,比如密码管理器,或者你的 Octopus Deploy 实例,最好使用一个 Azure 账户或者敏感变量。您还应该避免将它们提交到源代码控制中。

为了运行该应用程序,我们还将提供:

  • 资源组名称:Azure web app 要添加到哪个资源组。
  • WebApp 名称:赋予 Azure WebApp 的名称。
  • web App SKU:web App 使用什么类型的 App 服务计划
  • WebApp 位置:你想要托管 Azure WebApp 的数据中心位置。

要添加我们需要的参数,代码如下所示:

let azAppId = argv.[0]
let azSecret = argv.[1]
let azTenantId = argv.[2]
let azResourceGroupName = argv.[3]
let azWebAppName = argv.[4]
let azWebAppSku = argv.[5]
let azWebAppLocation = argv.[6] 

这将根据参数在命令行中的位置,分配程序运行时提供给程序的参数集合中的参数。

参数验证:
在这个例子中我没有显示参数验证,但是您可能想要考虑将它添加到您的 Farmer 模板中,以确保它们具有可接受的值。

定义 Azure 资源

有了参数值后,我们可以用 F#定义我们的 Azure WebApp:

let webAppSku = WebApp.Sku.FromString(azWebAppSku)
let webApp = webApp {
    name azWebAppName
    sku webAppSku
} 

这里我们将 WebApp SKU 赋给一个名为webAppSku的变量。这是由一个助手函数完成的,它返回一个强类型的Sku。然后我们使用 Farmer Web App builder 创建我们的webApp变量。

接下来,我们使用 Farmer ARM deployment builder 创建我们的 ARM 部署,在本例中,它由要部署到的位置和前面定义的 Azure WebApp 组成:

let deployLocation = Location.FromString(azWebAppLocation)
let deployment = arm {
    location deployLocation
    add_resource webApp
} 

内置型安全

在前面的两个代码示例中,F#类型系统发挥了自己的作用。不可能创建根据其类型无效的值。

让我们看一个例子。假设我想用一个值为VeryFreeSku来创建我们的 Azure WebApp。如果我试图在我们的应用程序中创建它,编译器会给我一个警告,它不会构建:

Farmer Compiler warning

这是因为编译器知道字符串值VeryFree是错误的类型,而应该是Sku类型。

这就是法默真正擅长手工制作手臂模板的地方。它使用 F#为您提供了类型安全,以确保您从一开始就拥有有效的模板。

农夫和手臂:
农夫和手臂模板这里有更详细的对比

生成手臂模板

当你对 Azure 资源建模后,Farmer 支持不同的方式来生成 ARM 模板。一种方法是将它直接写到文件中:

deployment |> Writer.quickWrite "output" 

然后,您可以使用您喜欢的方法将这个文件部署到 Azure。

部署到 Azure

除了生成 ARM 模板,您还可以选择让 Farmer 在应用程序运行时执行到 Azure 的部署。

需要 Azure CLI

如果您使用集成部署到 Azure 功能,您将需要在运行应用程序的计算机上安装 Azure CLI。

在我们的示例 SimpleAzureWebApp 应用程序中,我们将利用这个特性。

在执行部署之前,我们需要通过 Azure 进行身份验证。Farmer 附带了一个Deploy.authenticate命令,您可以通过传递之前提供给应用程序的凭证来调用它,如下所示:

Deploy.authenticate azAppId azSecret azTenantId
|> ignore 

当 authenticate 调用完成时,它返回与服务主体相关联的 Azure 订阅列表。在本例中,这些结果通过管道传递给ignore函数。

如果使用 Azure 进行身份验证时出现任何错误,将会引发一个错误。如果登录成功,我们需要使用Deploy.execute命令让 Farmer 执行我们的部署:

deployment
|> Deploy.execute azResourceGroupName Deploy.NoParameters
|> ignore 

您可以查询 ARM 部署的结果,但是与 authenticate 调用一样,我们忽略它们。类似地,部署中的任何错误都将作为异常出现。

完整的农民模板

这就是我们应用程序的全部内容。下面是完成的Program.fs文件:

open Farmer
open Farmer.Builders
open SimpleAzureWebApp.SkuExtension

[<EntryPoint>]
let main argv =

    let azAppId = argv.[0]
    let azSecret = argv.[1]
    let azTenantId = argv.[2]
    let azResourceGroupName = argv.[3]
    let azWebAppName = argv.[4]
    let azWebAppSku = argv.[5]
    let azWebAppLocation = argv.[6]

    let webAppSku = WebApp.Sku.FromString(azWebAppSku)
    let webApp = webApp {
        name azWebAppName
        sku webAppSku
    }

    let deployLocation = Location.FromString(azWebAppLocation)
    let deployment = arm {
        location deployLocation
        add_resource webApp
    }

    printf "Authenticating with Azure\n"
    Deploy.authenticate azAppId azSecret azTenantId
    |> ignore

    printf "Deploying Azure WebApp %s (%s) into %s using Farmer\n" azWebAppName azResourceGroupName azWebAppLocation

    deployment
    |> Deploy.execute azResourceGroupName Deploy.NoParameters
    |> ignore

    printf "Deployment of Azure WebApp %s (%s) complete!\n" azWebAppName azResourceGroupName

    0 // return an integer exit code 

打包农民模板

现在我们已经写好了应用程序,下一步是打包它以供 Octopus 使用。为了简单起见,我使用命令行工具来构建和打包应用程序,但是我建议将其作为完整 CI/CD 管道的一部分来自动化。

如果你是建筑新手。NET 核心应用程序,我们有许多指南,其中包括使用各种工具设置 CI/CD 管道的分步说明。

为了构建 SimpleAzureWebApp 应用程序,我们在应用程序目录中运行一个dotnet publish命令:

dotnet publish -o output 

这将构建并发布控制台应用程序,并将二进制文件放在output文件夹中,这是通过使用-o参数指定的。

接下来,我们需要打包应用程序,这次我们使用 Octopus CLI pack 命令:

octo pack --id SimpleAzureWebApp --format Zip --version 1.0.0.0 --basePath output 

这将生成一个名为SimpleAzureWebApp.1.0.0.0.zip的文件,该文件可以上传到 Octopus 内置存储库或外部包存储库

您可以使用 Octopus CLI 命令推送到 Octopus 内置存储库,推送:

octo push --package SimpleAzureWebApp.1.0.0.0.zip --server https://my.octopus.url --apiKey API-XXXXXXXXXXXXXXXX 

上传完包后,我们可以设置 Octopus 来运行我们的应用程序以部署到 Azure。

部署农民模板

Octopus 很酷的一点是你可以选择如何部署。随着去年运营手册的推出,这种灵活性进一步扩展到了运营任务,例如管理您的基础设施。

创建操作手册

为了执行我们的 Farmer 模板,我们将创建一个 runbook,将它部署到 Azure。为此:

  1. 在 Octopus 中创建新项目。
  2. 从操作➜操作手册部分进入操作手册流程。
  3. 点击添加 RUNBOOK
  4. 从概述中,点击定义您的 RUNBOOK 流程
  5. 点击添加步骤

在步骤选择中,选择运行脚本步骤,并为其命名。通过使用脚本步骤,我们可以使用引用包特性将我们的包作为脚本执行的一部分。

为了包含我们的包,在引用的包部分,点击 ADD 并添加我们之前上传的 SimpleAzureWebApp 包:

Add SimpleAzureWebApp Package Reference

保留所有默认设置,点击确定

添加 runbook 脚本

接下来,我们需要添加内联脚本来执行我们的 Farmer 模板。我们首先添加所需的 Azure 凭据:

$appId = $OctopusParameters["Project.Azure.Account.Client"]
$secret = $OctopusParameters["Project.Azure.Account.Password"]
$tenantId = $OctopusParameters["Project.Azure.Account.TenantId"] 

该脚本从名为Project.Azure.Account的项目变量中引用了许多扩展的 Azure 帐户变量属性,如ClientTenantId。这很方便,因为我们不需要为每个属性指定单独的变量。

有了凭证后,我们想要指定 Azure WebApp 参数,包括将传递给 SimpleAzureWebApp 的资源组和 WebApp 名称。NET 核心应用程序:

$resourceGroupName = $OctopusParameters["Project.Azure.ResourceGroupName"]
$webAppName = $OctopusParameters["Project.Azure.WebAppName"]
$webAppSku = $OctopusParameters["Project.Azure.WebAppSku"]
$webAppLocation = $OctopusParameters["Project.Azure.WebAppLocation"] 

最后,我们使用名为Octopus.Action.Package[SimpleAzureWebApp].ExtractedPath包变量获得提取的农民模板包的路径,然后将工作目录设置为该路径,并调用dotnet run命令传入我们的所有参数:

$farmerPackagePath = $OctopusParameters["Octopus.Action.Package[SimpleAzureWebApp].ExtractedPath"]
Set-Location $farmerPackagePath

dotnet SimpleAzureWebApp.dll $appId $secret $tenantId $resourceGroupName $webAppName $webAppSku $webAppLocation 

。NET Core 运行时先决条件

为了执行这个脚本步骤,它需要。NET Core runtime 安装在部署目标或 worker 上,该步骤配置为在其中执行。

添加变量

我们还需要添加上面脚本中引用的变量:

【T2 Project variables

Project.Azure.Account变量是一个 Azure 账户变量,其余都是文本变量。

运行运行手册

如果你已经做到了这一步,最后一部分是把所有的东西放在一起,在 Octopus 中运行我们的 runbook,并把 Farmer 模板部署到 Azure。

您可以看到一个运行到开发阶段的示例 runbook,它创建了名为farmer-webapp-dev的 Azure WebApp:

Farmer Azure Runbook run

runbook 运行完成后,您可以检查您的 WebApp 是否已使用 Azure portal 创建。下面是在 Azure 中创建的相应 WebApp,它是 runbook 运行到开发阶段的结果:

Azure portal Farmer webapp

结论

这种使用 Farmer 生成资源并将其部署到 Azure 的技术的优点在于,您可以对模板进行版本控制。定义基础设施的代码可以与运行在基础设施上的代码共存。另外,手动编辑 JSON 文件不再是一件麻烦的事情,谁不想要呢!

下次再见,愉快的部署!

了解更多信息

在您的 CI/CD 渠道中快速跟踪代码推广- Octopus 部署

原文:https://octopus.com/blog/fast-tracking-code-promotion-in-your-ci-cd-pipeline

Fast track code promotion in your CI/CD pipeline

软件开发团队在 2020 年面临的最大挑战之一是需要更快地将代码交付给生产。为了无缝地做到这一点,我们需要一个完全自动化的部署过程,并且它必须可以在多个环境中重复。此外,为了放心地部署到生产环境而不中断服务,我们必须在测试金字塔的每一层都成功通过测试。

定义这个过程可能具有挑战性,而可视化它可能更加困难;谢天谢地,Octopus Deploy 让我们两者都变得容易了!在这篇文章中,我定义了一个预先批准的生产就绪的部署管道,并讨论了所涉及的每个步骤的细节。

自动化测试金字塔

在深入我们的场景之前,让我们从定义我们的测试金字塔开始。一个完整的测试金字塔有四个模块。较低的块具有较高的测试数量,而较高的块具有较高的测试质量。完整的测试金字塔为我们的部署成功率提供了高度的信心:

来源:https://blog . octo . com/WP-content/uploads/2018/10/integration-tests-1024 x634 . png

如图所示,四层测试是:

  • 单元测试:这些测试需要在我们管道的持续集成阶段取得成功。
  • 组件测试:这些测试需要在我们管道的持续集成阶段取得成功。
  • 集成测试:这些测试需要在我们管道的连续交付阶段取得成功。
  • 端到端测试:在我们管道的连续交付阶段,这些测试必须成功。

方案

我们有一个正在生产中运行的/hello/world微服务,它当前返回一个 JSON 字段message,值为hello world!,我们希望在它成功返回时添加另一个名为status,值为200的响应字段:

{
    "message":"Hello World!",
    "status":200
} 

环境

组织策略规定,任何提升到生产环境的代码都必须部署在以下环境中:

  • 发展
  • 试验
  • 质量保证
  • 预生产
  • 生产

这些环境中的每一个都是静态集成环境,这意味着我们的应用程序的所有组件都存在于这些环境中。我们希望确保不会出现配置偏差。在后面的博文中,我将讨论如何让静态集成环境和短暂的动态环境共存于我们的 CI/CD 管道中。现在,我们将保持简单。

持续集成阶段

我们任何代码升级的目标都是构建一次,部署到任何地方。构建可部署对象显然是我们开始考虑部署它之前的第一步。在构建时,我们必须经历几个阶段:

  1. 预构建:代码林挺/格式化
  2. 预构建:单元测试
  3. 预构建:组件测试
  4. 预构建:静态代码分析
  5. 预构建:第三方库安全性分析
  6. 构建:二进制构建和打包
  7. 后期构建:将二进制文件推送到工件存储库

这些是在我们预先批准的部署流程中必须发生的最小可定义单元或阶段。每个阶段都必须根据由我们的工具管理员、信息安全和软件架构师配置的预定义规则成功通过。在连续集成阶段成功通过之后,我们就为连续交付阶段做好了准备。

连续交货

首先,我们来定义一下连续交付连续部署的区别。来自易消化的 DevOps:7 个 devo PS 实践:

交付:“一旦自动化测试验证了源代码的每一处变更,就为产品发布做好准备的实践。这包括自动构建、测试和部署。”

部署:“持续部署是努力实现端到端自动化生产部署的实践。”

对于连续交付,我们希望将可部署的二进制文件放到最低的集成环境中,以确保二进制文件:

  1. 根据需要执行。
  2. 运行我们最低要求的第三和第四阶段自动化测试,他们成功通过。
  3. 代码升级(或自动升级)的阶段。

通常,我们管道的持续交付方面作为持续集成管道的第二阶段来执行。对于开发人员来说,这是最容易混淆和最难解决的问题,因为这是传统的操作人员介入的地方。与管道的持续集成部分不同,我们在这里引入了许多新的故障点,包括:

  • 网络连接。
  • 名称空间冲突。
  • 事件驱动/事件触发的步骤未完成。
  • 混合部署问题和自动化测试问题。
  • 集成多种工具并对这些集成进行故障排除。

Octopus Deploy 的一个优点是它有一个内置的工件存储库,因此我们不必通过将工件存储在第三方位置或工具中来引入另一个潜在的故障点。这意味着我们可以确信,事实上,新的工件事件确实触发了我们管道中的连续交付步骤。我们应该清楚地定义我们的连续交付自动化和我们的集成/端到端测试阶段之间的区别,以便弄清楚在我们的管道中什么地方可能出现了故障。这些步骤可以定义为:

  • CD 二进制部署。
  • CD 二进制验证。
  • 集成测试光盘。
  • 端到端测试时的 CD。

有一个 CD 二进制文件验证阶段是很重要的,它可以确保我们不仅得到了需要的二进制文件,而且它确实按照预期执行了。在这种情况下,如果我们有一个坏的二进制文件(由于损坏或错误的代码逻辑等。),我们可以通过尽早失败并快速将反馈反馈给开发人员来节省大量时间。

什么时候测试?

代码升级的一个冲突方面是试图确保代码以相同的方式部署在每个环境中,同时只运行必要的测试以确保新的部署按预期工作。这方面的一个例子是在我们的 QA 环境中运行负载测试,而不是在其他环境中。在每个环境中运行负载测试将会非常耗时,并且会延长部署时间(这与我们试图实现的目标相反)。此外,我们可能不会在生产环境中运行负载测试,因为我们不想为实时服务的服务可靠性冒不必要的风险。

这就引出了一个问题,哪些测试需要运行,它们需要在什么时候运行,如何区分运行的测试,以及它们应该在什么环境中运行?

这些都是很好的问题,可以分为两类:

  • 预先批准的 CI/CD 管道。
  • 广泛的 CI/CD 渠道。

哪些变更可以通过预先批准的渠道进行?

并不是每一个代码变更都需要经过数小时的回归测试和负载测试。事实上,如果您遵循精益/敏捷开发方法,大多数代码更改不应该需要。如果变更很简单,并且没有从数据库中提取新数据或进行任何高级计算,那么该变更可能会被部署到生产环境中,而不需要进行全套测试,因为风险低且可信度高。

所有管道事件都应该从源代码控制中触发。为了确保更改将通过预先批准的 CI/CD 管道运行,我将创建一个新的 Git 分支,其命名约定如下:

feature-pa-eado-4287-add_status_response

  • feature:这意味着新的分支正在向代码库添加新的功能。
  • pa:这是一项预先批准的变更,将通过快速通道进行。
  • 这是我的 JIRA 项目。
  • <number>:这是我项目的一张 JIRA 门票。
  • add_status_response:这是对变更的快速描述。

默认情况下,所有更改将通过完整的 CI/CD 管道运行。这确保了开发人员对他们的变更投入足够的思考,以确定它是否可以被快速跟踪以部署到生产中。

在以后的博文中,我们将讨论非预先批准的变更将如何贯穿我们完整的 CI/CD 管道。

完整的预批准 CI/CD 渠道

  1. 部署前步骤(GitHub/吉拉):
    1. 一名开发人员被分配到 JIRA 机票 EADO-4287。根据需求,确定这是一个小的变化,并创建一个特征分支feature-pa-eado-4287-add_status_response
    2. 编写满足验收标准的单元测试。
    3. 编写满足单元测试的代码。
    4. 验证测试在本地成功通过,git push到源代码控制。
  2. 持续集成(Jenkins):
    1. 预构建:代码林挺/格式化。
    2. 预构建:单元测试。
    3. 预构建:组件测试。
    4. 预构建:静态代码分析。
    5. 预构建:第三方库安全分析。
    6. 预构建:请求已打开,暂停等待批准。
    7. 预构建:请求批准、合并、标记主分支(rc-<version>pa)。
    8. 构建:二进制构建并打包。
    9. 后期构建:将二进制文件推送到工件存储库(Octopus Deploy)。
  3. 开发环境(Octopus 部署):
    1. CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:dev_success
  4. 测试环境:
    1. Git 标签dev_success触发 CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:test_success
  5. 质量保证环境:
    1. Git 标签test_success触发 CD 二进制部署。将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:qa_success
  6. 生产前环境:
    1. Git 标签qa_success触发 CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:preprod_success
  7. 生产环境:
    1. Git 标签preprod_success触发 CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:v<version>
    6. CD post git 删除标签:
      1. rc-<version>
      2. dev_success
      3. test_success
      4. qa_success
      5. preprod_success

结论

将代码快速部署到生产环境中是开发团队面临的最大挑战。代码推广和部署都是关于自信的。为了获得这种信心,我们的 CI/CD 渠道必须包含测试金字塔的四层。确定何时在哪个部署环境中执行测试金字塔的每一层成为下一个挑战,但是为小的、低风险的更改定义预先批准的部署管道有助于平衡速度和质量,并且使用 Git 分支和标签来触发您预先批准的管道有助于将部署责任交给最了解代码的人,即开发人员。

浏览 DevOps 工程师手册以了解更多关于 DevOps、CI/CD 以及软件测试在持续交付中的作用。

通过执行 API - Octopus Deploy 加快部署

原文:https://octopus.com/blog/faster-deployments-with-the-executions-api

Executions API 是一组新的端点,可以显著提高部署、版本创建和 runbook 执行等操作的性能。在对这些操作进行审查后,我们设计并添加了这些端点,作为 Octopus 2022 Q3 版本的一部分。结果是这些操作的性能显著提高,部署时的可伸缩性更好。

在这篇文章中,我将深入探讨我们为什么创建 Executions API,以及它如何增强性能和开发人员体验。

使用 Octopus CLI 测量网络性能

今年早些时候,少数客户让我们知道了一个可以通过他们的部署来改善的问题。他们注意到构建环境和 Octopus REST API 之间有大量的网络流量。我们希望通过准确了解正在发生的事情来提高性能。

我们的调查从测量 Octopus CLI 生成的网络流量开始,以建立基线。Octopus CLI 广泛用于自动化场景,包括 Azure Pipelines、Buildkite、GitHub Actions、Jenkins 和 TeamCity。Octopus CLI 被用来创建和部署发行版,推送构建信息和软件包,以及运行操作手册。这些操作以 Octopus REST API 为目标来执行工作。

我们运行了一系列测试,涉及 3 个 Octopus CLI 操作:

  • create-release
  • deploy-release
  • run-runbook

我们跟踪了 Octopus CLI 和 Octopus REST API 之间发送的网络流量,并获得了它们的网络配置文件摘要:

操作 申请数量 持续时间 请求(正文) 回应(正文)
create-release 17 1060 毫秒 8.1 kB 96.4 kB
deploy-release 18 1275 毫秒 8.7 kB 105.6 kB
run-runbook 14 1452 毫秒 6.9 kB 90 kB

17 HTTP 请求支持像create-release这样的命令确实是一个非常闲聊的话题!对 Octopus CLI 中的create-release命令的检查显示,针对 Octopus REST API 发出了以下 HTTP 请求:

 1  200  CONNECT  /   
 2  200  GET      /api
 3  200  GET      /api
 4  200  GET      /api
 5  200  GET      /api/spaces
 6  200  GET      /api
 7  200  GET      /api/users/me
 8  200  GET      /api/users/{id}/spaces
 9  200  GET      /api/{id}
10  200  GET      /api/users/me
11  200  GET      /api/{id}/projects/projects?name={name}
12  200  GET      /api/{id}/projects/projects/{id}/channels
13  200  GET      /api/{id}/deploymentprocesses/{id}
14  200  GET      /api/{id}/projects/{id}/deploymentprocesses/template?channel={id}
15  200  GET      /api/{id}/projects/{id}/deploymentsettings
16  201  POST     /api/{id}/releases?{ignore-channel-rules}
17  200  GET      /api/{id}/releases/{id} 

第 16 行的 HTTP POST 是负责创建发布的 HTTP 请求。之前的大多数 HTTP 请求都内置在 Octopus CLI 中,以限定资源标识符(例如,项目的 ID 是什么,“octopes”?).这也代表了一条快乐的道路;如果遇到异常和/或错误并需要解决,则可能涉及更多网络流量。更糟糕的是,这些 HTTP 请求是按顺序发出的:

Screenshot of executions API http requests

这些操作在 Octopus REST API 中强制执行数据检查和决策树。如果一个发布涉及不同的包版本,这种情况会变得更糟,因为这可能会执行额外的服务操作。所有这些网络流量导致了更多的负载,损害了我们的可伸缩性。

我们需要一个更好的解决方案。我们在 2022 年第二季度开始工作,建立一组新的服务端点,统称为执行 API。这些 API 极大地减少了 Octopus REST API 和诸如 Octopus CLI 之类的 API 客户端之间发送的网络流量。

执行 API 简介

执行 API 由以下操作和路线组成:

操作 途径
create-release /api/{space-id}/releases/create/v1
run-runbook /api/{space-id}/runbook-runs/create/v1
tenanted-deployment /api/{space-id}/deployments/create/tenanted/v1
untenanted-deployment /api/{space-id}/deployments/create/untenanted/v1

这些 API 包含了 Octopus CLI 执行的所有“繁重”工作。这不仅减少了网络流量,还使这项工作更接近数据。结果是执行 API 比通过 Octopus REST API 表示的集合调用快 3 倍:

Graph comparing the Executions API and Octopus Rest API

更好的开发人员执行 API 体验

除了解决性能问题之外,我们还希望 Executions API 能够提供出色的开发人员体验。我们通过以下方式实现了这一目标:

  1. 将版本控制合并到所有路线中
  2. 将版本控制合并到所有消息中
  3. 将 camel case 应用于消息模式
  4. 将资源名称(尽可能地)应用于消息模式

将版本化策略合并到执行 API 中具有重要的战略意义。我们知道 API 可以存在很长时间,需要进化。将版本应用于路由和消息为我们提供了一条途径,将更改合并到我们的 API 客户端库和现有集成中。

消息模式中键的骆驼大小写提供了更好的开发人员体验,因为它符合客户的期望:

{
  "channelName": "Default",
  "gitRef": "refs/heads/main",
  "projectName": "OctoPetShop",
  "releaseVersion": "1.2.3",
  "spaceId": "Spaces-2006",
  "spaceIdOrName": "Pattern - Tenants"
} 

使用 Executions API,您可以在消息有效负载中为资源指定名称而不是 id(只要有可能)。这可以节省您执行查找的时间,从而获得更好的整体开发体验。

下一步是什么?

Executions API 极大地提高了部署、版本创建和 runbook 执行等操作的性能,并且可供运行 Octopus Deploy 2022.3 的客户使用。

我们的下一步是将它们整合到我们现有的集成和工具中。已经开始将执行 API 与新的 Octopus CLI 结合起来。将新的 Octopus CLI 与 Executions API 结合使用时,您应该会注意到部署时间显著缩短。

愉快的部署!

特性分支 web 应用程序- Octopus 部署

原文:https://octopus.com/blog/feature-branch-web-apps

octopus hanging off a branch in a pot plant

Octopus 非常擅长管理开发、测试和生产环境中的变更进度。它还通过使用通道很好地处理了分支策略,如 hotfixes,允许您绕过某些环境,在紧急情况下将具有匹配版本规则的包(如在版本发布字段中有单词hotfix)直接推向生产。

但是特征分支呢?在这篇博客文章中,我详细分析了什么是特性分支,以及如何在 Octopus 中管理它们。

什么是特征分支?

在我们能够在 Octopus 中建模特征分支之前,我们需要理解什么是特征分支。

马丁·福勒提供了很好的描述:

特性分支是一种源代码分支模式,当开发人员开始处理一个新特性时,她会打开一个分支。她在这个分支上完成特性的所有工作,并在特性完成时将变更与团队的其他成员集成在一起。

我怀疑大多数开发人员都熟悉在一个特性分支中工作,但是我们感兴趣的是与部署相关的细节,包含在短语在这个分支上做所有的特性工作。

具体来说,我们希望为开发人员提供一种部署他们正在开发的代码的方式:

  • 在不容易在本地复制的环境中进行测试,例如平台即服务(PaaS)产品。
  • 轻松地与团队的其他成员分享他们工作的当前状态。
  • 为额外的测试提供稳定的目标,如端到端、性能或安全性测试。

不像固定的环境,比如测试或者生产,特性分支是短暂的。它们在一个特性被开发时存在,但是一旦合并到一个主线分支中,这个特性分支就应该被删除。

此外,功能分支并不打算部署到生产中。与热修复不同,热修复是快速解决关键问题的紧急生产部署,功能分支仅用于测试。

通过限制某个功能分支的受众,您还可以潜在地节省成本。这是因为您可以在晚上删除部署及其底层基础设施,然后在早上重新部署特性分支。这样做意味着你不再花钱去托管那些一夜之间没人会用到的应用程序。

特性分支通常由 CI 系统处理,这是一种确保测试通过并产生可部署工件的便捷方式。有一个令人信服的论点是,CI 系统应该产生一个可部署的工件(如果代码编译的话),而不管测试结果如何,假设像测试驱动开发(TDD)这样的过程鼓励失败的测试作为开发工作流的正常部分。

版本控制功能分支工件

像 GitVersion 这样的工具提供了一些例子,展示了包含在 SemVer 预发布字段中的特性分支名称,产生了类似于1.3.0-myfeature的版本。大多数包管理工具都有公开组件以适应特性分支名称的版本控制策略。

在包管理器没有版本控制指南的情况下,比如 Docker 存储库,采用 SemVer 这样的版本控制方案是一个不错的选择。

Maven 版本控制方案有一个怪癖,带有限定符的版本,如1.0.0-myfeature,被认为是比未限定的版本,如1.0.0更晚的版本。但是,使用通道版本规则意味着代表功能分支的合格版本不适合部署到生产环境,因此合格和不合格版本的排序在 Octopus 中不存在问题。

功能分支部署的质量

考虑到以上所有因素,我们可以将功能分支部署定义为具有以下品质:

  • 它们仅用于测试,不会在生产环境中公开。
  • 它们是短暂的,仅在版本控制系统中存在相应的分支时才存在。
  • 在工作时间之外可能不需要它们,如果部署可以在一夜之间停止,则可以节省成本。
  • 它们不会在任何地方得到提升,因此它们的生命周期包括在测试环境中的一次部署。
  • 他们的应用程序工件被版本化以识别源特性分支。

下一步是在 Octopus 中对上述规则进行建模。

Octopus 包含了有限的元步骤概念,我们将使用这个术语对修改 Octopus 本身的步骤进行分类。

Deploy a release 步骤就是一个例子,顾名思义,它可以用于部署为另一个项目创建的发布。脚本步骤还可以动态生成一些章鱼资源

元步骤功能有些特别和有限。我们需要一个全面的解决方案来创建和销毁 Octopus 中的资源,以反映功能分支的短暂性质。

Octopus CLI 提供了一些额外的功能,能够创建许多 Octopus 资源,如版本、频道和环境。不幸的是,它不包括删除这些资源的所有相应选项,所以它不是一个完整的解决方案。

另一种选择是使用 REST API,它公开了 web UI 可以执行的每个操作。然而,如果可能的话,我宁愿避免编写第二个 CLI 工具来管理 Octopus 资源的整个生命周期。

幸运的是,章鱼平台供应商正好提供了我们需要的东西。将这个提供者与 Octopus 中现有的 Terraform 部署步骤结合起来,我们可以创建自己的元步骤。这反过来意味着我们可以管理表示特征分支所需的短暂 Octopus 资源。

管理功能分支的操作手册

我们将利用 runbooks 来支持创建和删除短暂的 Octopus 资源。这允许我们将底层基础设施的管理与应用程序的部署分开。

我们将创建六本操作手册。三个 runbooks 将为单个功能分支创建、删除和暂停资源。根据 Git 中是否存在分支,这些将与另外三个 runbooks 配对执行:

  • 创建分支机构基础设施,这将创建部署单个分支机构所需的资源。
  • 恢复分支,这将基于 Git 存储库中的分支基础设施(重新)创建分支基础设施。
  • 破坏分支基础设施,这将破坏特征分支资源。
  • 销毁分支,这将删除 Git 中删除的分支资源的任何特性分支资源。
  • 暂停分支基础设施,这将在不使用时关闭或破坏昂贵的功能分支资源。
  • 挂起分支,这将挂起所有分支。

将章鱼资源映射到特色分支

Octopus 有几个资源来促进功能分支的部署,但是对于这些资源如何与功能分支相关联,并没有硬性的规则。我们将在这里提供一些意见,指导我们如何构建上面列出的操作手册。

环境

我们将使用环境来封装用于托管特性分支的资源。环境提供了一个很好的范围和安全边界,并且清楚地将特性分支与静态环境(如生产环境)分开。

生活过程

为了防止特性分支被提升到生产环境,或者根本不被提升,我们将为每个特性分支创建一个生命周期,包括上面的单个环境。

频道

为了确保适当的特性分支工件被部署到正确的环境中,我们将使用一个通道。通道规则将确保特性分支工件版本被选择并导向正确的生命周期,这反过来确保正确的环境接收特性分支部署。

在这三种资源中,我们将通道视为特性分支的表示。您在部署期间选择通道,它是 Octopus 项目的唯一本地资源(生命周期和环境的范围是一个空间)。这使得它成为在项目中表现一个特性分支的自然选择。

租户也可以用来表示一个功能分支。我发现环境、渠道和生命周期是更自然的匹配。

我们部署到的 web 应用程序将由目标来表示。这些目标将被限定在它们相关的功能分支环境中。

Web 应用程序有插槽的概念,插槽也可以用来部署特性分支。然而,插槽是 Azure 服务计划上的有限资源,这意味着您将受限于任何时候可以部署的功能分支的数量。因此,本博客为每个功能分支使用了新的网络应用。

最初的 Azure 帐户

为了在 Azure 中部署功能分支 web 应用,我们需要一个初始的“种子”Azure 帐户来管理云资源。这是运行 Terraform 脚本的帐户:

我们的元步骤将像任何其他 Terraform 部署一样执行。这意味着即使 Octopus 在管理自己,我们也需要创建变量来允许 Terraform 部署返回到 Octopus 服务器。

  1. 创建一个新的 Octopus 项目,并定义两个名为 ApiKeyServerUrl 的变量。API 密钥将是一个保存有 Octopus API 密钥的秘密变量,服务器 URL 将是 Octopus 服务器的 URL。在这个例子中,我使用了一个 URL 为https://mattc.octopus.app云实例
  2. 此外,创建一个名为 FeatureBranch 的提示变量。这将用于将功能分支的名称传递到 runbooks 中。
  3. 最后,创建一个名为 Azure 的变量,该变量指向上一节中创建的 Azure 帐户:

【T2

部署项目

我们的部署项目将利用部署 Azure 应用服务步骤从octopussamples/randomquotes JavaDocker 存储库中部署 Docker 映像。存储库包含使用 GitHub 上的代码构建的图像,带有正确的标记规则以识别特征分支。

称该步骤为Deploy Web App。我们将在下一节创建通道时引用这个步骤名。

《创建分支机构基础设施操作手册》

我们将从创建构建所有资源的 runbook 开始,包括 Octopus 和 Azure,以部署一个特性分支。

这个 Terraform 模板创建了 Azure web 应用基础设施。我们将它保存在一个单独的模板中,这样我们可以单独销毁这些资源,而不会破坏 Octopus 资源。该模板将被部署在名为 Feature Branch Web App 的步骤中(我们稍后将使用步骤名称来引用输出变量):

provider "azurerm" {
version = "=2.0.0"
features {}
}

variable "apiKey" {
    type = string
}

variable "space" {
    type = string
}

variable "serverURL" {
    type = string
}

terraform {
  backend "azurerm" {
    resource_group_name  = "mattc-test"
    storage_account_name = "storageaccountmattc8e9b"
    container_name       = "terraformstate"
    key                  = "#{FeatureBranch}.azure.terraform.tfstate"
  }
}

resource "azurerm_resource_group" "resourcegroup" {
  name     = "mattc-test-webapp-#{FeatureBranch}"
  location = "West Europe"
  tags = {
    OwnerContact = "@matthew.casperson"
    Environment = "Dev"
    Lifetime = "15/5/2021"
  }
}

resource "azurerm_app_service_plan" "serviceplan" {
  name                = "#{FeatureBranch}"
  location            = azurerm_resource_group.resourcegroup.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "Standard"
    size = "S1"
  }
}

resource "azurerm_app_service" "webapp" {
  name                = "mattctestapp-#{FeatureBranch}"
  location            = azurerm_resource_group.resourcegroup.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
  app_service_plan_id = azurerm_app_service_plan.serviceplan.id
}

output "ResourceGroupName" {
  value = "${azurerm_resource_group.resourcegroup.name}"
}

output "WebAppName" {
  value = "${azurerm_app_service.webapp.name}"
} 

下一个 Terraform 模板创建了一个 Octopus 环境,将其置于生命周期中,并在通道中对其进行配置。它还创建了一个 web 应用程序目标:

variable "apiKey" {
    type = string
}

variable "space" {
    type = string
}

variable "serverURL" {
    type = string
}

terraform {
  required_providers {
    octopusdeploy = {
      source  = "OctopusDeployLabs/octopusdeploy"
    }
  }

  backend "azurerm" {
    resource_group_name  = "mattc-test"
    storage_account_name = "storageaccountmattc8e9b"
    container_name       = "terraformstate"
    key                  = "#{FeatureBranch}.terraform.tfstate"
  }
}

provider "octopusdeploy" {
  address  = var.serverURL
  api_key   = var.apiKey
  space_id = var.space
}

resource "octopusdeploy_environment" "environment" {
  allow_dynamic_infrastructure = true
  description                  = "Feature branch environment for #{FeatureBranch}"
  name                         = "#{FeatureBranch}"
  use_guided_failure           = false
}

resource "octopusdeploy_lifecycle" "lifecycle" {
  description = "The lifecycle holding the feature branch #{FeatureBranch}"
  name        = "#{FeatureBranch}"

  release_retention_policy {
    quantity_to_keep    = 1
    should_keep_forever = true
    unit                = "Days"
  }

  tentacle_retention_policy {
    quantity_to_keep    = 30
    should_keep_forever = false
    unit                = "Items"
  }

  phase {
    name                        = "#{FeatureBranch}"
    is_optional_phase           = false
    optional_deployment_targets = ["${octopusdeploy_environment.environment.id}"]

    release_retention_policy {
      quantity_to_keep    = 1
      should_keep_forever = true
      unit                = "Days"
    }

    tentacle_retention_policy {
      quantity_to_keep    = 30
      should_keep_forever = false
      unit                = "Items"
    }
  }
}

resource "octopusdeploy_channel" "channel" {
  name       = "Feature branch #{FeatureBranch}"
  project_id = "#{Octopus.Project.Id}"
  lifecycle_id = "${octopusdeploy_lifecycle.lifecycle.id}"
  description = "Repo: https://Github.com/OctopusSamples/RandomQuotes-Java Branch: #{FeatureBranch}"
  rule {
    id = "#{FeatureBranch}"
    tag = "^#{FeatureBranch}.*$"
    action_package {
      deployment_action = "Deploy Web App"
    }
  }
}

resource "octopusdeploy_azure_service_principal" "azure_service_principal_account" {
  application_id  = "#{Azure.Client}"
  name            = "Azure account for #{FeatureBranch}"
  password        = "#{Azure.Password}"
  subscription_id = "#{Azure.SubscriptionNumber}"
  tenant_id       = "#{Azure.TenantId}"
}

resource "octopusdeploy_azure_web_app_deployment_target" "webapp" {
  account_id                        = "${octopusdeploy_azure_service_principal.azure_service_principal_account.id}"
  name                              = "Azure Web app #{FeatureBranch}"
  resource_group_name               = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[ResourceGroupName]}"
  roles                             = ["Web Application", "Web Application #{FeatureBranch}"]
  tenanted_deployment_participation = "Untenanted"
  web_app_name                      = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[WebAppName]}"
  environments                      = ["${octopusdeploy_environment.environment.id}"]
  endpoint {
    default_worker_pool_id          = "WorkerPools-762"
    communication_style             = "None"
  }
} 

在这个模板中有一些重要的东西需要强调。

Octopus provider 是一个官方插件,可以通过 Terraform 自动下载:

 required_providers {
    octopusdeploy = {
      source  = "OctopusDeployLabs/octopusdeploy"
    }
  } 

我们已经利用 Octopus 模板语法(后跟花括号的散列符号)构建了 Terraform 模板的各个部分。后端块不能使用 Terraform 变量,但是因为 Octopus 在将文件传递给 Terraform 之前对其进行了处理,所以我们可以绕过这一限制,将变量注入到键名中:

 backend "azurerm" {
    resource_group_name  = "mattc-test"
    storage_account_name = "storageaccountmattc8e9b"
    container_name       = "terraformstate"
    key                  = "#{FeatureBranch}.terraform.tfstate"
  } 

对通道的描述指出了通道所代表的 Git 存储库和分支。因为我们决定通道是项目中一个特性分支的表示,所以将它链接回它所来自的代码库所需的细节将在这里被捕获。

还要注意,我们引用前面创建的步骤Deploy Web App来将通道规则应用于:

resource "octopusdeploy_channel" "channel" {
  name       = "Feature branch #{FeatureBranch}"
  project_id = "#{Octopus.Project.Id}"
  lifecycle_id = "${octopusdeploy_lifecycle.lifecycle.id}"
  description = "Repo: https://Github.com/OctopusSamples/RandomQuotes-Java Branch: #{FeatureBranch}"
  rule {
    id = "#{FeatureBranch}"
    tag = "^#{FeatureBranch}.*$"
    action_package {
      deployment_action = "Deploy Web App"
    }
  } 

为了创建 Azure 帐户,我们创建了一个新帐户,其详细信息与在 Terraform 步骤中定义的 Azure 帐户相同:

在一个更安全的环境中,这些细节将被一个仅限于必要资源的帐户所取代。在不太安全的环境中,您可以完全跳过创建功能分支特定帐户,而只需共享现有帐户:

resource "octopusdeploy_azure_service_principal" "azure_service_principal_account" {
  application_id  = "#{Azure.Client}"
  name            = "Azure account for #{FeatureBranch}"
  password        = "#{Azure.Password}"
  subscription_id = "#{Azure.SubscriptionNumber}"
  tenant_id       = "#{Azure.TenantId}"
} 

当创建目标时,我们利用由第一个 Terraform 脚本创建的输出变量。资源组名输出为#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[ResourceGroupName]},web app 名输出为#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[WebAppName]}

这个目标还专门为健康检查定义了一个 Windows worker 池(在我的例子中,ID 为WorkerPools-762)。这是因为 Azure web 应用目标需要 Windows 工作线程来运行运行状况检查:

resource "octopusdeploy_azure_web_app_deployment_target" "webapp" {
  account_id                        = "${octopusdeploy_azure_service_principal.azure_service_principal_account.id}"
  name                              = "Azure Web app #{FeatureBranch}"
  resource_group_name               = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[ResourceGroupName]}"
  roles                             = ["Web Application", "Web Application #{FeatureBranch}"]
  tenanted_deployment_participation = "Untenanted"
  web_app_name                      = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[WebAppName]}"
  environments                      = ["${octopusdeploy_environment.environment.id}"]
  endpoint {
    default_worker_pool_id          = "WorkerPools-762"
    communication_style             = "None"
  } 

本操作手册的最后一步是调用 Octopus CLI 来创建一个版本并将其部署到新环境中。通过运行部署,我们可以确信,当我们在早上重新创建特性分支时,它们将部署最新的应用程序代码。

如果已经创建了一个分支,但是没有可用于部署的工件,我们允许这个调用失败:

octo create-release \
    --project="Random Quotes" \
    --channel="Feature branch #{FeatureBranch}" \
    --deployTo="#{FeatureBranch}" \
    --space="#{Octopus.Space.Name}" \
    --server="#{ServerUrl}" \
    --apiKey="#{ApiKey}"

# allow failure if the a package is not available
exit 0 

通过运行本操作手册:

  • Octopus 将填充所有的功能分支资源。
  • Azure 中创建了一个 web 应用程序。
  • 部署最新版本的功能分支应用程序(如果存在)。

因为 Terraform 是等幂的,所以我们可以多次重新运行这个 runbook,任何丢失的资源都将被重新创建。我们将利用这一点,在一夜之间销毁网络应用程序,以节省成本。

简历分支运行手册

Create Branch infra structure run book 可以为分支创建资源,但是它不知道 Git 中存在哪些分支。 Resume Branches runbook 扫描 Git repo 以查找分支,并执行Create Branch infra structurerun book 以在 Octopus 中反映这些分支。

下面的 PowerShell 解析对ls-remote --heads https://repo的调用结果,并使用结果列表来调用创建分支基础结构 runbook:

$octopusURL = "https://mattc.octopus.app"
$octopusAPIKey = "#{ApiKey}"
$spaceName = "#{Octopus.Space.Name}"
$projectName = "#{Octopus.Project.Name}"
$environmentName = "#{Octopus.Environment.Name}"
$repos = @("https://Github.com/OctopusSamples/RandomQuotes-Java.Git")
$ignoredBranches = @("master", "main")

# Get all the branches for the repos listed in the channel descriptions    
$branches = $repos |
    % {PortableGit\bin\Git ls-remote --heads $_} |
    % {[regex]::Match($_, "\S+\s+(\S+)").captures.groups[1].Value} |
    % {$_.Replace("refs/heads/", "")} |
    ? {-not $ignoredBranches.Contains($_)}

# Clean up the old branch infrastructure
$branches |
    % {Write-Host "Creating branch $_";
        octo run-runbook `
          --apiKey $octopusAPIKey `
          --server $octopusURL `
          --project $projectName `
          --runbook "Create Branch Infrastructure" `
          -v "FeatureBranch=$_" `
          --environment $environmentName `
          --space $spaceName} 

销毁分支机构基础设施操作手册

销毁分支基础设施创建分支基础设施相反,它删除所有资源,而不是创建它们。这是通过使用与在创建分支基础设施操作手册中定义的相同的 Terraform 模板来实现的,但是使用销毁 Terraform 资源步骤而不是应用 Terraform 模板步骤来执行它们。

摧毁树枝操作手册

破坏分支运行手册与恢复分支运行手册相反。它向 Octopus 查询所有通道,向 Git 查询所有分支,并且在通道没有匹配分支的情况下,调用销毁分支基础结构 runbook:

Install-Module -Force PowershellOctopusClient
Import-Module PowershellOctopusClient

$channelDescriptionRegex = "Repo:\s*(\S+)\s*Branch:\s*(\S+)"

# Octopus variables
$octopusURL = "#{OctopusUrl}"
$octopusAPIKey = "#{ApiKey}"
$spaceName = "#{Octopus.Space.Name}"
$projectName = "#{Octopus.Project.Name}"

$endpoint = New-Object Octopus.Client.OctopusServerEndpoint $octopusURL, $octopusAPIKey
$repository = New-Object Octopus.Client.OctopusRepository $endpoint
$client = New-Object Octopus.Client.OctopusClient $endpoint

$space = $repository.Spaces.FindByName($spaceName)
$repositoryForSpace = $client.ForSpace($space)

$project = $repositoryForSpace.Projects.FindByName($projectName)
$channels = $repositoryForSpace.Channels.GetAll() | ? {$_.ProjectId -eq $project.Id}

# Get all the project channels whose description is in the format "Repo: http://blah Branch: blah"
$repos = $channels |
    % {[regex]::Match($_.Description, $channelDescriptionRegex)} |
    ? {$_.Success} |
    % {$_.captures.groups[1]} |
    Get-Unique

# Get all the branches for the repos listed in the channel descriptions    
$branches = $repos |
    % {@{Repo = $_; Branch = $(PortableGit\bin\Git ls-remote --heads $_)}} |
    % {$repo = $_.Repo; [regex]::Match($_.Branch, "\S+\s+(\S+)").captures.groups[1].Value.Replace("refs/heads/", "") | %{"Repo: $repo Branch: $_"}}

# Get all the branches that have a channel but have been removed from the repo
$oldChannels = $channels |
    ? {[regex]::Match($_.Description, $channelDescriptionRegex).Success} |
    ? {-not $branches.Contains($_.Description)} |
    % {[regex]::Match($_.Description, $channelDescriptionRegex).captures.groups[2]}

# Clean up the old branch infrastructure
$oldChannels |
    % {Write-Host "Deleting branch $_";
        octo run-runbook `
          --apiKey $octopusAPIKey `
          --server $octopusURL `
          --project $projectName `
          --runbook "Destroy Branch Infrastructure" `
          -v "FeatureBranch=$_" `
          --environment $_ `
          --space $spaceName} 

暂停分支机构基础设施运行手册

为了一夜之间节省资金,我们将销毁 Azure 中的功能分支 web 应用。这是微软提出的暂停应用服务计划的解决方案。

删除 Azure 资源也只是运行销毁 Terraform 资源步骤的一个例子,模板在创建分支基础设施操作手册中的功能分支 Web 应用步骤中。因为我们只破坏 Azure 资源,所以任何现有的 Octopus 版本、渠道、环境、目标和帐户都将保留。

由于我们在删除和创建这些 Azure 资源时使用相同的后端状态文件,Terraform 知道任何给定 Azure 资源的状态。这意味着我们可以根据需要调用暂停分支基础设施创建分支基础设施销毁分支基础设施run book,Terraform 将让 Azure 处于所需的状态。

暂停分支运行手册

暂停分支运行手册扫描 Octopus 寻找通道,并调用暂停分支基础设施运行手册。这意味着任何与渠道相关的 Azure 资源,以及 Octopus 中的功能分支都将被销毁,并且不再需要花钱:

Install-Module -Force PowershellOctopusClient
Import-Module PowershellOctopusClient

$channelDescriptionRegex = "Repo:\s*(\S+)\s*Branch:\s*(\S+)"

# Octopus variables
$octopusURL = "#{ServerUrl}"
$octopusAPIKey = "#{ApiKey}"
$spaceName = "#{Octopus.Space.Name}"
$projectName = "#{Octopus.Project.Name}"
$environment = "#{Octopus.Environment.Name}"

$endpoint = New-Object Octopus.Client.OctopusServerEndpoint $octopusURL, $octopusAPIKey
$repository = New-Object Octopus.Client.OctopusRepository $endpoint
$client = New-Object Octopus.Client.OctopusClient $endpoint

$space = $repository.Spaces.FindByName($spaceName)
$repositoryForSpace = $client.ForSpace($space)

$project = $repositoryForSpace.Projects.FindByName($projectName)
$channels = $repositoryForSpace.Channels.GetAll() | ? {$_.ProjectId -eq $project.Id}

# Get all the project channels whose description is in the format "Repo: http://blah Branch: blah"
$branches = $channels |
    % {[regex]::Match($_.Description, $channelDescriptionRegex)} |
    ? {$_.Success} |
    % {$_.captures.groups[2].Value} |
    Get-Unique

# Clean up the old branch infrastructure
$branches |
    % {Write-Host "Deleting branch $_";
        octo run-runbook `
          --apiKey $octopusAPIKey `
          --server $octopusURL `
          --project $projectName `
          --runbook "Suspend Branch Infrastructure" `
          -v "FeatureBranch=$_" `
          --environment $environment `
          --space $spaceName} 

同步 Octopus 和 Git

我们现在有了管理功能分支资源所需的所有操作手册。为了保持 Octopus 和 Git 同步,下一步是调度触发器来删除旧的分支、关闭资源和创建新的分支。

我们首先在一天中的不同时间点触发销毁分支操作手册。该 runbook 将破坏功能分支资源,其中底层功能分支已从 Git 中删除。

清理这些分支不应该干扰任何人,因为我们假设删除底层分支意味着该特性已经被合并或放弃。所以我们创建一个触发器,每小时执行一次销毁分支运行手册。

同样,我们希望捕捉全天创建的任何新分支,因此我们安排 Create Branches runbook 在早上 6 点到下午 6 点之间定期运行,以覆盖平均工作日。

为了降低成本,我们希望在一天结束时删除托管我们的功能分支的云资源。为此,我们安排暂停分支机构运行手册在晚上 7 点运行。这确保它在最终的创建分支 runbook 触发器之后运行。

最终结果是:

  • 通过销毁分支,全天清理被删除的分支。
  • 在工作日开始时,任何新的分支都会被检测到,所有 Azure 资源都会通过创建分支重新创建。
  • 晚上所有 Azure 资源都被暂停分支破坏:

如果使用触发器的响应能力不足以捕捉新分支的创建和销毁,也可以直接从 Git 本身执行octo run-runbook。GitHub 等流行的托管服务提供工具,允许在创建和销毁分支时运行脚本,这使得 Octopus 中特性分支的创建和清理几乎是即时的。

看章鱼的特征分支

有了这些操作手册并适当触发后,我们现在可以回顾在 Octopus 中创建的资源来表示我们的特性分支。

以下是渠道:

以下是环境:

以下是生命周期:

以下是目标:

这些是账目:

这里是发布创建,我们选择一个要部署到的通道/特性分支,并让 Octopus 匹配通道规则来为我们选择正确的特性分支工件:

结论

Terraform 应用和销毁步骤,与 Octopus Terraform 提供程序一起,为我们提供了创建元步骤以在 Octopus 中实现短期特性分支所需的工具。由于 Terraform 的幂等性质,我们有一组可靠地管理我们短暂的 Azure 和 Octopus 资源的健壮步骤。

通过一些自定义脚本来同步 Octopus 与 Git 分支和调度触发器或来自 GitHub 等托管平台的直接触发器,我们可以确保 Octopus 反映我们代码库中正在开发的功能分支。

观看网络研讨会

https://www.youtube.com/embed/NRwFdpvNYyA

VIDEO

我们定期举办网络研讨会。请参见网络研讨会第页,了解过去的网络研讨会以及即将举办的网络研讨会的详细信息。

愉快的部署!

特征优先化:投票胜过一切吗?-章鱼部署

原文:https://octopus.com/blog/feature-prioritization

在我们的用户之声网站上,功能需求和建议通常介于两个极端之间:

  1. 使我能够做一些目前不可能做的事情
  2. 让目前可能做的事情变得稍微容易一点

作为一个软件发行商,我们的资源是有限的,我们不能对每一个建议都采取行动。对要采取行动的建议进行优先排序是很重要的,但这通常看起来是随机的,尤其是当投票数很少的项目在投票数很多的项目之前实施时。区分特性的优先级应该完全由投票决定吗?或者这应该是一个判断电话?

在决定采纳哪些建议时,我认为有许多因素需要考虑:

  • 有多少人想要这个功能,他们想要多少(投票)?
  • 有可接受的变通方法吗?
  • 变通方法有多困难/理想?
  • 该特性实现起来有多困难?
  • 支持该功能的持续成本是多少?
  • 我们能从这个特性中获益多久?

三个例子

举例来说,这里有三个关于 Octopus Deploy UserVoice 网站的建议:

  1. 安排发布 (18 票)
  2. 选择多个环境部署到 (40 票)
  3. 恢复对 ie 8 的支持 (3 票)

当您在 Octopus 中部署一个版本时,部署当前会立即执行。第一个建议意味着用户可以指定部署开始的时间。它可能看起来像这样:

Choosing when to deploy

当前的解决方法是要么在凌晨 2 点进入办公室,要么在凌晨 2 点安排一个可能运行也可能不运行的任务,该任务使用我们的 API 或命令行工具来触发部署。

第二个建议是,我们可以选择多个环境,而不是选择一个环境进行部署:

Selecting multiple environments to deploy to

解决方法是简单地部署到一个环境,回到这个屏幕,选择下一个环境,然后再次部署。或者使用我们的 API 或命令行工具并指定要部署到的多个环境。

第三个建议不言自明——恢复对 IE8 的支持。目前我们只支持 IE9 或以上版本。

我们应该先做哪一个?

前两个建议可能有相同的难度——它们都可以很快完成。它们也是非常孤立的特性,不会影响产品中的许多其他特性。一旦我们构建了它们,并添加了一些测试来确保它们能够工作,这些就是我们可以忘记的特性。我喜欢这类功能。

“多种环境”比“预定部署”的票数多。但是“预定部署”的解决方法远不如“多环境”的解决方法理想。所以总的来说,我们可能会首先实现“预定部署”,尽管它有一半的票数。

IE 8 支持怎么样?

这个很有意思。第一,很难;我们使用的一些库也不能很好地支持 IE8,所以我们必须解决这个问题。然后,在我们说我们“支持它”之前,我们必须测试应用程序的所有/大部分都是可用的。

但像这样的功能伴随着持续的成本:每当我们实现一个新功能,我们也必须用 IE 8 测试它。不像其他两个,我们永远不能“忘记”这个特性。与自动化浏览器验收测试相比,编写一个集成测试来确保预定的部署在正确的时间发生要容易得多。

IE8 的变通办法也很有趣:变通办法是存在的(升级 IE,换一个不同的浏览器)。但由于成本(或政治)的原因,这些变通办法虽然简单,但并不是所有客户都能接受的。老实说,我发现当一个解决方案存在,但由于这些原因被拒绝时,我比解决方案很难(例如,凌晨 2 点上班)时更少同情。

还有一个事实是,IE8 的市场份额正在萎缩;我们可以花一周的时间来增加对它的支持,再加上额外的时间来测试我们增加的每个功能/回应关于 IE8 问题的错误报告,然后在 6 个月内,无论如何没有人会需要它。

为此,我决定将 IE8 建议标记为拒绝。不仅仅是比其他两个特性优先级低;由于持续的支持成本和最终的淘汰,这实际上是一个负面特性。我可以让它开放,以避免伤害建议者的感情,但现实是,即使它有 50 票,我们只是永远不会这样做。

我很想知道:你同意这个推理吗?确定功能优先级时,还应该考虑哪些因素?建议的受欢迎程度应该是关键驱动因素吗?

特色步骤模板:HTTP -测试 URL - Octopus Deploy

原文:https://octopus.com/blog/featured-step-template-http-test-url

无论是预热 web 服务器还是简单地测试一个新的部署,点击一个 URL 并验证结果是 web 应用程序部署过程中常见的事情。

虽然总是有可能用 Octopus Deploy 为这个编写一个 PowerShell 步骤, Sporting Solutions 已经足够友好地将这个打包到一个易于重用的模板中,用于 Octopus Deploy 库

因为这是我们第一次在 Octopus Deploy 博客上展示贡献的 step 模板,所以让我们来看看如何将它引入到您的 Octopus 项目中。

导入模板

首先要做的是访问库并找到模板。你可以在这里找到 HTTP - Test URL 模板。

Library view

该库本身提供了模板的快速概述,以便您可以确定它是否符合您的要求;不出所料,这个页面上最重要的是绿色的“复制到剪贴板”按钮。

现在按它来检索描述模板的 JSON 文档。

接下来,登录你的 Octopus Deploy 服务器,访问库>步骤模板。在页面的右上角有一个导入链接——点击它,然后粘贴你刚刚从网站上复制的 JSON。

Import dialog with JSON included

一旦您点击了 Import ,就会显示模板——如果您愿意,现在可以调整参数或脚本,但除此之外,模板已经准备好用于您的部署过程。

Template in library

使用模板

假设您有一个安装 web app 的部署流程的项目,第一步是打开项目>流程选项卡,并选择添加步骤按钮。在显示的列表底部,您会看到刚刚导入的模板的名称。

Process view

当您单击模板名称来创建新步骤时,您将看到每个模板参数的输入字段。在 HTTP - Test URL 的情况下,这是要测试的 URL、预期的状态代码和超时值。

Step view

本例中的 URL 等步骤模板参数的工作方式就像普通的 Octopus 步骤参数一样——它们可以使用#{Variable}替换来绑定,每个参数本身作为一个变量公开,可以在其他地方使用,例如在 PowerShell 脚本中。

结果!

最后——这是最激动人心的部分:)—当您创建并部署项目的新版本时,该步骤将会运行,结果将会打印到部署日志中。

Success!

我们真的很喜欢 HTTP - Test URL 模板,因为尽管它非常简单,但它是使用 step 模板和 Octopus Deploy 库可以做什么的一个很好的例子。如果您还没有浏览过这个库,您可能会惊讶地发现有多少模板已经可用了。

如果你已经在编写 step 模板并想与 Octopus 社区分享,现在参加我们的库竞赛还不算太晚;如果你提交一个模板,并在 6 月底前被接受,你就可以自豪地拥有一个 Octopus Deploy 马克杯!

愉快的部署!

二月社区综合报道-八达通部署

原文:https://octopus.com/blog/february-community-roundup

部署像 Octopus 这样的产品的一个真正酷的事情是当客户开始写他们自己的关于它的博客帖子。我们将开始整理这些帖子的列表,以便您可以看到世界各地的其他人在做什么。

这是二月份发生的事情的综述(我知道晚了几天)。

IANA 任务

首先,当 IANA 正式承认我们的触手通信端口(10933)时,我们有了一个“哇,太酷了”的时刻。

在社区里

最近不是只有我们在忙。我们的一些优秀客户已经写了关于他们成功使用 Octopus Deploy 的博客文章。

我们必须对卡罗林·克莱弗大呼小叫,她写了一系列关于向她的组织和所有客户介绍 Octopus Deploy 的文章。听起来像一次伟大的旅行!同样是关于文化变革的话题, Martyn Frank 在博客上讲述了他在引入 Octopus 和自动化后对团队文化的体验。

我们的朋友伊恩·波林继续他关于 Octopus 变化的帖子,并对 3.0 中发生的事情做了很好的总结。

Gregor Suttie 也已经开始给 Octopus Deploy 试驾,我们期待着那里更多的帖子。

最后,大卫·罗伯兹发布了名为 OctoPygmy 的谷歌 Chrome 扩展来定制界面,并对环境和仪表盘进行过滤和分组。看起来很酷!

这是二月份的综述,请关注几周后的下一篇!

节日科技日历-八达通黑客马拉松-八达通部署

原文:https://octopus.com/blog/festive-tech-calendar-hackathon

Octopus Deploy 是今年节日科技日历的一部分,举办黑客马拉松比赛。

黑客马拉松的目标是使用 Octopus Deploy 部署 Azure Web 应用程序。

什么是节日科技日历?

Festive Tech Calendar 是一个为期一个月的免费社区科技活动。邀请所有技术社区的人提交演讲、内容和竞赛,并参与活动。

章鱼黑客马拉松

什么:使用 Octopus Deploy 部署一个 Azure Web 应用,然后填写我们的黑客马拉松提交表单

何时:在 2021 年 12 月 1 日星期三至 12 月 15 日星期三之间完成练习

奖品:三个八爪鱼布包之一

黑客马拉松指南

我们的指导方针很宽松:

  • 我们不介意你如何配置你的部署或者最终的 web 应用是什么,只要你使用 Octopus Deploy 部署 Azure
  • 无论您的应用程序和流程是简单还是复杂
  • 您可以构建一个新的 web 应用程序或使用 Octopus 示例之一:
  • 你可以使用对你来说新的或者你熟悉的技术,从 ARM 模板、PowerShell、Terraform 到 GitHub 动作等等

首先,我们只是想让你玩得开心,学点东西!

如何进入

  • 使用现有的 Azure 帐户或注册一个 Azure 免费帐户
    • 如果你注册了 Azure 免费帐户,你将自动获得 30 天 200 美元的 Azure 点数和 12 个月有限数量的额外免费服务
  • 使用现有的 Octopus Deploy 实例或注册一个 Octopus Cloud 免费试用版
  • 使用 Octopus Deploy 部署 Azure Web 应用程序
  • 完成黑客马拉松后,请在 2021 年 12 月 15 日星期三格林威治时间下午 5 点之前填写我们的黑客马拉松提交表格中的所有必填字段

优胜者

我们将从提交的作品中随机选出三名获胜者,他们将获得我们的一个礼品包:

1st prize comprising octopus hoodie, tee and hat, 2nd prize comprising octopus hoodie and hat, 3rd prize comprising octopus tee and hat

我们将在 2021 年 12 月 20 日星期一格林威治时间下午 2 点左右宣布获胜者。

随意写一篇博客,创建一个视频,或者发一条关于你的黑客马拉松实现的微博,标记@ octopus deploy;我们希望收到您的来信。

这些可选的活动不会给你额外的学分,但会传播一些社区精神。

细则

  • 购买或付款不是必要的,也不会增加你中奖的机会。
  • 我们将只考虑每人一份参赛作品。
  • 黑客马拉松不向 Octopus Deploy 的员工、代表或代理人开放,包括他们的所有直系亲属和家庭成员。
  • 黑客马拉松不向参与设计、制作、推广、执行或分发黑客马拉松的任何其他个人开放,包括他们的所有直系亲属和家庭成员。
  • 黑客马拉松提交期:格林威治时间 2021 年 12 月 1 日上午 8 点–格林威治时间 2021 年 12 月 15 日下午 5 点。
  • 获胜者将于 2021 年 12 月 20 日(星期一)格林威治时间下午 2 点左右宣布。

Octopus Deploy 入门

如果您以前没有使用过 Octopus Deploy,请查看以下资源:

愉快的部署!

Selenium 系列:通过 XPaths 和 CSS 选择器查找元素

原文:https://octopus.com/blog/selenium/6-finding-elements-by-xpaths-and-css-selectors/finding-elements-by-xpaths-and-css-selectors

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

在上一节中,我们通过 ID 属性搜索了想要与之交互的元素。使用 ID 属性非常方便,但是在现实世界的场景中,并不是每个想要与之交互的元素都有 ID,或者 ID 是由模板语言生成的,而不是静态的。

与 ID 属性不同,网页中的每个元素都有唯一的 XPath。XPath (XML Path Language)是一种查询语言,用于从类似 XML 的文档中选择节点,比如我们的例子中的 HTML。

XPath 查询可能会变得非常复杂。独特的 XPaths 看起来像//*[@id="request-summary"]/div/div[2]/div/form/div[2]/input并不罕见。

幸运的是,Chrome 和其他浏览器提供了一种为元素生成唯一 XPaths 的简单方法。右键单击Elements选项卡中的元素并选择复制➜复制 Xpath 会将标识该元素的最简洁的唯一 Xpath 放入剪贴板。这个功能意味着您不需要理解 XPath 的本质细节就可以使用它们,因为您可以让浏览器为您生成 XPath。

对于我们的测试网页,Chrome 生成的 XPath 利用了这样一个事实,即元素都有唯一的 ID 属性,这导致了相对简洁的 XPath,如//*[@id="button_element"]

为了使用 XPaths 而不是 id 来定位页面中的元素,我们需要向AutomatedBrowser接口添加四个新方法:

void clickElementWithXPath(String xpath);

void selectOptionByTextFromSelectWithXPath(String optionText, String xpath);

void populateElementWithXPath(String xpath, String text);

String getTextFromElementWithXPath(String xpath); 

反过来,我们将这四个方法的实现添加到AutomatedBrowserBase类中:

@Override
public void clickElementWithXPath(final String xpath) {
  if (getAutomatedBrowser() != null) {
      getAutomatedBrowser().clickElementWithXPath(xpath);
  }
}

@Override
public void selectOptionByTextFromSelectWithXPath(final String optionText, final String xpath) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().selectOptionByTextFromSelectWithXPath(optionText, xpath);
  }
}

@Override
public void populateElementWithXPath(final String xpath, final String text) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().populateElementWithXPath(xpath, text);
  }
}

@Override
public String getTextFromElementWithXPath(final String xpath) {
  if (getAutomatedBrowser() != null) {
    return getAutomatedBrowser().getTextFromElementWithXPath(xpath);
  }
  return null;
} 

然后我们在WebDriverDecorator类中定义这些方法的实现。

注意,我们在这些方法中调用By.xpath()而不是By.id()。这是我们根据元素的 XPath 搜索元素的方法:

@Override
public void clickElementWithXPath(final String xpath) {
  webDriver.findElement(By.xpath(xpath)).click();
}

@Override
public void selectOptionByTextFromSelectWithXPath(final String optionText, final String xpath) {
  new Select(webDriver.findElement(By.xpath(xpath))).selectByVisibleText(optionText);
}

@Override
public void populateElementWithXPath(final String xpath, final String text) {
  webDriver.findElement(By.xpath(xpath)).sendKeys(text);
}

@Override
public String getTextFromElementWithXPath(final String xpath) {
  return webDriver.findElement(By.xpath(xpath)).getText();
} 

最后,我们创建了一个新的测试方法,它与我们的示例 web 页面进行交互,但是这次利用了新的基于 XPath 的方法:

@Test
public void formTestByXPath() throws URISyntaxException {

  final AutomatedBrowser automatedBrowser = AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Chrome");

  try {
    automatedBrowser.init();

    automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());

    automatedBrowser.clickElementWithXPath("//*[@id=\"button_element\"]");
    assertEquals("Button Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithXPath("//*[@id=\"text_element\"]", "test text");
    assertEquals("Text Input Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithXPath("//*[@id=\"textarea_element\"]", "test text");
    assertEquals("Text Area Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.selectOptionByTextFromSelectWithXPath("Option 2.1", "//*[@id=\"select_element\"]");
    assertEquals("Select Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"radio3_element\"]");
    assertEquals("Radio Button Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"checkbox2_element\"]");
    assertEquals("Checkbox Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"image_element\"]");
    assertEquals("Image Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"div_element\"]");
    assertEquals("Div Clicked", automatedBrowser.getTextFromElementWithId("message"));
  } finally {
    automatedBrowser.destroy();
  }
} 

像 XPaths 一样,HTML 文档中的所有元素都有一个唯一的 CSS 选择器来标识它们。

CSS 选择器与 CSS 规则集中使用的标识符相同。如果你做过任何网页开发,那么你很可能熟悉 CSS 选择器。但即使你不是,Chrome 和其他浏览器也提供了一种为 HTML 元素生成 CSS 选择器的方法。在 Chrome 中,右击Elements标签中的元素,选择复制➜复制选择器。

在我们的例子中,因为我们正在交互的元素都有 ID 属性,这个菜单选项将把一个 CSS 选择器如#button_element放入剪贴板。

使用 CSS 选择器的过程与我们支持 XPaths 的过程非常相似。

我们在AutomatedBrowser接口中定义新方法:

void clickElementWithCSSSelector(final String cssSelector);

void selectOptionByTextFromSelectWithCSSSelector(final String optionText, final String cssSelector);

void populateElementWithCSSSelector(final String cssSelector, final String text);

String getTextFromElementWithCSSSelector(final String cssSelector); 

然后,我们在AutomatedBrowserBase类中提供默认实现:

@Override
public void clickElementWithCSSSelector(final String cssSelector) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().clickElementWithCSSSelector(cssSelector);
  }
}

@Override
public void selectOptionByTextFromSelectWithCSSSelector(final String optionText, final String cssSelector) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().selectOptionByTextFromSelectWithCSSSelector(optionText, cssSelector);
  }
}

@Override
public void populateElementWithCSSSelector(final String cssSelector, final String text) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().populateElementWithCSSSelector(cssSelector, text);
  }
}

@Override
public String getTextFromElementWithCSSSelector(final String cssSelector) {
  if (getAutomatedBrowser() != null) {
    return getAutomatedBrowser().getTextFromElementWithCSSSelector(cssSelector);
  }
  return null;
} 

用新方法更新了WebDriverDecorator类。我们使用By.cssSelector()方法通过元素的 CSS 选择器来搜索元素:

@Override
public void clickElementWithCSSSelector(final String cssSelector) {
  webDriver.findElement(By.cssSelector(cssSelector)).click();
}

@Override
public void selectOptionByTextFromSelectWithCSSSelector(final String optionText, final String cssSelector) {
  new Select(webDriver.findElement(By.cssSelector(cssSelector))).selectByVisibleText(optionText);
}

@Override
public void populateElementWithCSSSelector(final String cssSelector, final String text) {
  webDriver.findElement(By.cssSelector(cssSelector)).sendKeys(text);
}

@Override
public String getTextFromElementWithCSSSelector(final String cssSelector) {
  return webDriver.findElement(By.cssSelector(cssSelector)).getText();
} 

最后,我们将所有新方法与使用 CSS 选择器定位元素的新测试结合在一起:

@Test
public void formTestByCSSSelector() throws URISyntaxException {
  final AutomatedBrowser automatedBrowser = AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Chrome");

  try {
    automatedBrowser.init();

    automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());

    automatedBrowser.clickElementWithCSSSelector("#button_element");
    assertEquals("Button Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithCSSSelector("#text_element", "test text");
    assertEquals("Text Input Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithCSSSelector("#textarea_element", "test text");
    assertEquals("Text Area Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.selectOptionByTextFromSelectWithCSSSelector("Option 2.1", "#select_element");
    assertEquals("Select Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithCSSSelector("#radio3_element");
    assertEquals("Radio Button Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithCSSSelector("#checkbox2_element");
    assertEquals("Checkbox Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithCSSSelector("#image_element");
    assertEquals("Image Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithCSSSelector("#div_element");
    assertEquals("Div Clicked", automatedBrowser.getTextFromElementWithId("message"));
  } finally {
    automatedBrowser.destroy();
  }
} 

我们已经看到了三种不同的方法来识别 HTML 页面中的元素:使用 ID、使用 XPaths 和使用 CSS 选择器。但是哪种方式是最好的呢?

在有效的 HTML 中,id属性必须是唯一的。在一个设计良好的页面中,id属性还为定义它的元素提供了一些有意义的上下文。当开发人员更新应用程序时,元素在页面中移动时,通常也会保留相同的id属性。这使得通过 ID 查找元素成为在页面中定位元素的最简洁可靠的方式。

不幸的是,因为id属性必须手动分配给一个元素,所以您不能依赖于您希望与之交互的具有id属性的元素。事实上,以我的经验来看,在针对现实世界的应用程序编写测试时,很少有id属性可用。

id属性不同,所有元素都可以使用 XPaths 和 CSS 选择器来定位。因此,在没有可用 ID 的情况下,您将不得不求助于这些定位器中的一个。

CSS 选择器往往比 XPaths 更熟悉,因为 CSS 选择器被 web 开发人员用来设计 CSS 规则集。出于这个原因,我推荐 CSS 选择器而不是 XPaths。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

Selenium 系列:Firefox 调试技巧- Octopus 部署

原文:https://octopus.com/blog/selenium/12-firefox-debugging-tips/firefox-debugging-tips

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

我在使用代理时多次遇到的一个常见问题是 Firefox 中一个微妙的错误配置,它会导致抛出错误。

为了模拟这个错误,让我们尝试在BrowserMobDecorator类中配置 SOCKS 代理。

SOCKS 代理用于代理 TCP 数据包,这意味着它们可以用于 HTTP、HTTPS、FTP 和一系列其他高级协议。我们不会使用 BrowserMob 作为 SOCKS 代理,但是在这里配置它是演示错误配置错误的一个有用的方法。

我们通过调用seleniumProxy.setSocksProxy(proxyStr)来配置 SOCKS 代理:

@Override
public DesiredCapabilities getDesiredCapabilities() {

  proxy = new BrowserMobProxyServer();

  proxy.start(0);

  final Proxy seleniumProxy = new Proxy();
  final String proxyStr = "localhost:" + proxy.getPort();
  seleniumProxy.setHttpProxy(proxyStr);
  seleniumProxy.setSslProxy(proxyStr);
  seleniumProxy.setSocksProxy(proxyStr);

  final DesiredCapabilities desiredCapabilities =
    getAutomatedBrowser().getDesiredCapabilities();

  desiredCapabilities.setCapability(CapabilityType.PROXY, seleniumProxy);

  return desiredCapabilities;
} 

在 Firefox 中以这种配置运行测试将导致抛出下面的异常:

org.openqa.selenium.SessionNotCreatedException: InvalidArgumentError: Expected [object Undefined] undefined to be an integer
Build info: version: '3.12.0', revision: '7c6e0b3', time: '2018-05-08T14:04:26.12Z'
System info: host: 'Christinas-MBP', ip: '192.168.1.84', os.name:
'Mac OS X', os.arch: 'x86_64', os.version: '10.13.5', java.version: '1.8.0_144'
Driver info: driver.version: FirefoxDriver
remote stacktrace: WebDriverError@chrome://marionette/content/error.js:178:5
InvalidArgumentError@chrome://marionette/content/error.js:305:5
assert.that/<@chrome://marionette/content/assert.js:405:13
assert.integer@chrome://marionette/content/assert.js:256:10
assert.positiveInteger@chrome://marionette/content/assert.js:274:3
fromJSON@chrome://marionette/content/session.js:291:28
match_@chrome://marionette/content/session.js:458:23
fromJSON@chrome://marionette/content/session.js:427:12
GeckoDriver.prototype.newSession@chrome://marionette/content/driver.js:693:25
despatch@chrome://marionette/content/server.js:293:20
execute@chrome://marionette/content/server.js:267:11
onPacket/<@chrome://marionette/content/server.js:242:15
onPacket@chrome://marionette/content/server.js:241:8
_onJSONObjectReady/<@chrome://marionette/content/transport.js:500:9 

从异常中的消息来看,似乎某些远程 JavaScript 代码导致了一个错误。但是这段 JavaScript 代码在哪里,我们如何调试错误呢?

查看 JavaScript 堆栈跟踪,错误的来源似乎在这两行代码中。一个名为fromJSON()的方法断言一个整数是正的,这个断言失败了:

assert.positiveInteger@chrome://marionette/content/assert.js:274:3
fromJSON@chrome://marionette/content/session.js:291:28 

调试这个错误的关键是要理解文件chrome://marionette/content/session.js是和 Firefox 捆绑在一起的。如果你在 Firefox 中输入这个网址,你可以看到文件的来源。

在这种情况下,有问题的代码行是:

p.socksVersion = assert.positiveInteger(json.socksVersion); 

从这段代码中,我们可以推断出我们需要定义 SOCKS 代理的版本。

理论上,您可以用下面的代码在BrowserMobDecorator类中定义 SOCKS 代理版本:

@Override
public DesiredCapabilities getDesiredCapabilities() {
  // ...
  seleniumProxy.setSocksVersion(5);
  // ...
} 

实际上,WebDriver 库中的错误仍然会导致代码失败。然而,从这篇文章中得到的重要信息是,当您看到 Firefox stack traces 带有以chrome://marionette/开头的 URL 时,您可以通过直接在 Firefox 中输入 URL 来访问这些文件的源代码,以便调试根本原因。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

詹金斯 X - Octopus 部署初探

原文:https://octopus.com/blog/first-look-at-jenkins-x

Jenkins X first look

作为一个免费的开源构建服务器,Jenkins 被数百万人使用,所以大多数开发者都使用过或者至少听说过 Jenkins。像大多数构建服务器一样,Jenkins 通常安装在服务器上,以使用源代码,在构建代理上执行构建过程,并部署或发布生成的工件。

从概念上讲,这个构建服务器的模型对我来说很容易理解,它适用于当今大多数流行的解决方案,如 Team City、Azure DevOps、Bamboo 等。所以第一次和 Jenkins X 合作的时候,很自然的,我尝试用同样的概念模型去理解。这被证明是一个错误。

我真的很难理解 Jenkins X 是什么,但是在一些尝试和错误之后,我回去重读了关于第页的Jenkins X。深藏在文本中的两句话对于理解詹金斯 X 至关重要:

詹金斯 X 固执己见。

要注意的关键是,你需要从你可能已经有的任何詹金斯经验中清除你的头脑。

内化这两个陈述对于理解詹金斯 X 是什么是至关重要的。考虑到这一点,让我们从高层次上来看看詹金斯 x。

它始于库伯内特星团

从字面上看,Kubernetes 是一个容器编排器。你描述你想要运行的容器,它们如何相互通信,它们需要什么资源,Kubernetes 做执行一切的艰苦工作。通常,Kubernetes 托管长期运行的应用程序,如不断等待请求的 web 服务器,Kubernetes 的一个重要特性是它将监控这些长期运行的进程,并在它们失败时重新启动它们。

这种对 Kubernetes 的传统看法是你首先要忘记的。要理解 Jenkins X,可以把 Kubernetes 更像一个云操作系统。

你使用apt-get在 Linux 中安装一个应用程序,同样,Kubernetes 可以使用 Helm 安装应用程序。

就像您可以使用docker CLI 运行短期命令来构建您的应用程序和 Docker 映像一样,Kubernetes 可以使用ska fold构建软件和 Docker 映像。

由于您将托管 Docker 图像或其他图像工件库,如 Nexus 作为随操作系统启动的服务,因此您可以将这些相同的应用程序部署到 Kubernetes。

Jenkins X 将您的 Kubernetes 集群视为一个运行构建的环境,一种托管工件存储库的方式,一个 Helm charts 的安装目标,最后,一个部署和运行您构建的应用程序的地方。

通过安装 Jenkins X,您将拥有一个自包含的 Kubernetes 集群,其中包含精选和定制配置的服务,可以开始构建和部署应用程序。

这扩展到了您的本地开发环境

我习惯于我的本地开发环境与中央构建服务器完全分离。为了让我的代码进入构建服务器,我通常将它推到一个中央 GIT 存储库,转到 CI 服务器,将一个项目指向 GIT repo,配置构建,然后启动它。

如果我的 CI 服务器提供了 CLI 工具,它专门用于管理 CI 服务器。这样的工具对我正在处理的代码没有任何概念。

同样,忘记您从传统 CI 服务器中学到的一切。Jenkins X 对如何构建和部署您的代码有自己的看法,并不羞于为您配置一切。

要让 Jenkins X 构建您的现有代码,您可以运行jx import。运行该命令将向您的项目添加几个文件,包括jenkins-x.yml(这是 Jenkins X 管道)、Dockerfile(这构建了 Docker 映像)、skaffold.yaml(这是ska fold 项目配置)和一个charts目录(这提供了掌舵图模板)。

Jenkins X 然后将所有代码放入 GIT 存储库中,推送它,并开始构建。

正如您从下面的输出中看到的,Jenkins X 将许多不同的工具和服务结合在一起,以创建一个可重复的构建过程:

PS C:\Users\Matthew\Downloads\ThymeleafSpring> jx import
WARNING: No username defined for the current Git server!
? Do you wish to use mcasperson as the Git user name: Yes
The directory C:\Users\Matthew\Downloads\ThymeleafSpring is not yet using git
? Would you like to initialise git now? Yes
? Commit message: Initial import

Git repository created
selected pack: C:\Users\Matthew\.jx\draft\packs\github.com\jenkins-x-buildpacks\jenkins-x-kubernetes\packs\maven
? Which organisation do you want to use? mcasperson
replacing placeholders in directory C:\Users\Matthew\Downloads\ThymeleafSpring
app name: thymeleafspring, git server: github.com, org: mcasperson, Docker registry org: kubernetes-demo-198002
skipping directory "C:\\Users\\Matthew\\Downloads\\ThymeleafSpring\\.git"
Using Git provider GitHub at https://github.com
? Using organisation: mcasperson
? Enter the new repository name: ThymeleafSpring
Creating repository mcasperson/ThymeleafSpring
Pushed Git repository to https://github.com/mcasperson/ThymeleafSpring
Creating GitHub webhook for mcasperson/ThymeleafSpring for url http://hook.jx.35.194.232.107.nip.io/hook

Watch pipeline activity via:  jx get activity -f ThymeleafSpring -w
Browse the pipeline log via:  jx get build logs mcasperson/ThymeleafSpring/master
You can list the pipelines via: jx get pipelines
When the pipeline is complete: jx get applications

For more help on available commands see: https://jenkins-x.io/developing/browsing/ 

构建和部署将它们联系在一起

如果说传统 CI 服务器有一个好处的话,那就是因为您手动设置了一切,所以您对代码如何从源代码库流向最终部署有一个相当好的想法。

因为 Jenkins X 为我们做了这么多工作,所以很难理解构建过程中实际发生了什么。所以让我们来看看幕后的一些工作。

作为 Kubernetes 集群初始化的一部分,Jenkins X 安装了 Nexus 存储库管理器。构建完成后,我们可以看到我们的应用程序所依赖的 Java 依赖项现在被缓存在本地。这种缓存意味着任何后续构建都将更快地完成。

Jenkins X 安装的另一个服务是 ChartMuseum,这是一个 Helm 存储库。通过下载index.yaml文件,我们可以看到 Jenkins X 已经创建了一个舵图并发布到这个内部存储库中:

curl http://chartmuseum.jx.35.194.232.107.nip.io/index.yaml
apiVersion: v1
entries:
  jenkinx-spring-demo:
  - apiVersion: v1
    appVersion: 0.0.1
    created: "2019-08-28T19:25:22.445582847Z"
    description: A Helm chart for Kubernetes
    digest: 9e048b78247da2a28562771742454484dbaf35b10d14a0c0a668c3ba826d02c4
    icon: https://raw.githubusercontent.com/jenkins-x/jenkins-x-platform/master/images/java.png
    name: jenkinx-spring-demo
    urls:
    - charts/jenkinx-spring-demo-0.0.1.tgz
    version: 0.0.1
generated: "2019-08-28T19:32:00Z" 

Jenkins X 还负责将 Docker 映像发布到本地容器注册中心。因为我们在 Google Cloud Kubernetes 集群中运行 Jenkins X,所以 Jenkins X 默认使用 Google 容器注册表。然而,如果该服务不可用,Jenkins X 就会在 Kubernetes 集群中安装 Docker 注册中心。

最后,我们可以看到 Helm 图表已经部署在集群中,并且入口规则已经由 exposecontroller 创建,这导致我们的应用程序公开了一个自定义主机名。

结论

当你第一次遇到 Jenkins X 时,可能很难理解它到底是什么。如果从这篇文章中有什么收获的话,那就是忘记你所知道的关于 CI 服务器的一切,并欣赏 Jenkin X 本身。

Jenkins X 不仅仅是一种运行构建的方式,它提供了一个完整的构建生态系统和自以为是的工作流,从准备本地代码结构开始,到部署 Kubernetes 应用程序结束。

欣赏 Jenkins X 需要从大多数开发人员与开发管道交互的方式上进行思维转变,但好处是一个结构良好的工作流和紧密配置的工具链。

我在 Octopus - Octopus Deploy 工作的第一年

原文:https://octopus.com/blog/first-year-working-at-octopus

本周,我迎来了在 Octopus 工作的一周年纪念日,我想分享一下在这里工作的感受并谈谈我们的文化会很棒。我之前在我的个人博客上写了我的第一印象,但是我想更深入地挖掘一下。

船舶

首先也是最重要的,章鱼有航运文化!我加入 Octopus 的时候,团队正准备推出 Octopus 3.0。这是一个巨大的里程碑,看着最后一个 bug 被击败并发布出去是令人兴奋的。也就是说,我实际上已经开始着手改进Octopus.com的功能,包括增强我们的销售和许可渠道,以及构建超级升级页面,让客户更容易升级和更新他们现有的许可证。

不久之后,我开始在 Octopus Deploy 上工作,进行错误修复和增强,并为更大的功能发布做出贡献。很高兴看到像ChannelsElastic EnvironmentsMulti-tenancy这样的新功能成形并进入客户手中。我喜欢我们为新功能发布 RFC(征求意见),社区提供反馈来帮助塑造产品。我喜欢的另一件事是,我们定期发布新版本,只要它们准备好了(有时每周几次),而不是更长的发布周期。这是尽早向我们的客户提供最新功能和修复的好方法。

我们甚至自动化了我们的发布过程,这样任何人都可以在任何时间轻松完成。这大大减少了运输的摩擦!有趣的是,我们确实用章鱼装运章鱼!

我们即将发布 Octopus 3.4,并开始一些非常酷的新工作。这是一个非常令人兴奋的时刻在八达通部署!

信任

我们定期发货,并努力发送高质量的版本!信任是实现这一目标的关键。无论我们做什么,整个团队都相互信任和支持。从我们的创始人到最新的团队成员,每个人都愿意随时分享或帮助。这是非常重要的,因为八达通是一个远程友好的公司,我们没有一个深层次的管理。除了需要合作的时候,团队的大部分成员都在家工作。Octopus 的总部设在澳大利亚的布里斯班,我们在中央商务区或市中心有一个办公室,在那里我们可以工作和协作。这是专注的远程工作和面对面交流的完美平衡。

我们是章鱼!

基于信任,八达通建立了一个伟大的团队,他们关心自己所做的事情。每个人都有长处和短处,但我们都愿意分享和帮助他人。周围都是很棒的人,感觉很棒。我的同事达米安·布雷迪指出,我们称章鱼为we,而不是说itthe company。我们都乐于谈论章鱼并代表它。我们都是被信任的。我们是一个团队。我们是章鱼!

当章鱼开始参加会议并且我们开始与人们面对面交谈时,这一点得到了加强。我们代表公司帮助人们理解我们解决的问题和我们提供的价值。

远程工作

向远程工作的转变既有趣又有益。我绝对不怀念每天上下班的时间,这让我有更多时间在家陪妻子和两个年幼的儿子。家庭干扰有时会很有挑战性,但好处远远大于代价。我经常在推特上谈论我的工作生活平衡,我从来没有这么好过。

上次我写了以下内容,它仍然是非常正确的。

我可以更经常地见到我的妻子和孩子,我们一起吃午饭,如果我们需要为家庭跑腿,我也会在身边。这极大地改善了我与妻子和孩子的关系,我不认为我会回到过去。

成长

我以第 12 名员工的身份加入了 Octopus,我们的团队页面现在有 19 人,不久将有 2 人加入。我们已经超出了我们的办公室,因此团队已经从每周三与办公室的每个人一起进行 sprint 审查转变为在不同的日子进行协作。我们也开始做每周一次的远程友好 TL;每周三进行简短的演示,其他时间进行更深入的演示。总的来说,变化是积极的,但我们仍在学习和适应。

我们成长过程中最大的变化是,我们正在过渡到一种开放的分配管理风格,在这种风格下,所有团队成员在日常工作中都有很大的自由度。 Paul 帮助设定总体方向,但我们可以自由地推出新功能,并选择我们认为可以做出最大贡献的团队/任务组。

章鱼团队的新成员与一个伙伴配对,他们在办公室一起工作一周,或者直到他们适应在家工作。我是几个新团队成员的好朋友,这是一个了解新团队成员并让他们快速开始工作的好方法。我们还举办了为期两天的迷你章鱼大学训练营,以帮助我们的一些技术水平较低的团队成员了解软件开发的挑战和手动部署的痛苦。这很有趣,每个人都学到了很多东西!

包裹

我喜欢在八达通的第一年,我真的期待更多!再说一次,可以肯定地说,我是一只快乐的章鱼,未来非常光明。

灵活工作-杰森的故事-章鱼部署

原文:https://octopus.com/blog/flexible-work-week-jason

Octo-flexi

弹性工作在很多方面都可以改变游戏规则。我是 Jason,Octopus Deploy 的云架构师,我将解释为什么它不仅改变了我的游戏规则,而且改变了我的生活。

当我在 2017 年 6 月加入 Octopus Deploy 时,我已经在悉尼生活了近 19 年,老实说,我已经准备好改变了。

在现代悉尼,你有三种生活方式。你要么为住在 CBD 附近支付过高的租金,要么应对非同寻常的通勤,要么试图在两者之间取得平衡。

在我 15 公里的自行车通勤路上与危险的司机发生了太多的冲突后,我放弃了寻求平衡的努力,将我工资的一大部分交给了 Rozelle 的一个仓库,那里距离我以前的工作单位只有 3 公里。当然,我没有一天花三四个小时在火车上,我也不太可能被骑自行车的穿白色衣服的人撞倒,但我仍然压力很大,经常不开心。

Octopus Deploy 是一个远程优先的工作场所。我的许多同事在布里斯班,其他人在墨尔本、阿德莱德、美国,甚至阿根廷。加入 Octopus 后不久,我几乎准备好收拾行李搬到布里斯班加入我的新团队。

当然,结果是章鱼云团队大部分时间都在墨尔本,很适应远程工作,所以我留在了原地。但是悉尼机器仍然在磨我,我骑自行车更少,我现在没有明显的理由支付过高的租金。

老实说,我很不开心。更重要的是,我意识到多年来我一直不快乐。

我需要改变。

于是一个想法开始萌发。

几年前,当我开始认真地骑自行车时,我去了趟维多利亚东北部的布莱特镇。第一次拜访之后,我会一次又一次地回去。

A panorama from Mount Porepunkah

布莱特坐落在群山之中,是严肃的公路自行车手和山地车手所熟知的。它被纵横交错的森林包围,有适合全天探险的极好的砾石小径,在很短的车程内有五六个山地自行车公园,并且是澳大利亚一些最好的公路骑行的基地。因为它是一个旅游城市,所以它的基础设施也很好,靠近两个滑雪场,有足够的咖啡馆、酒吧和娱乐场所,让养尊处优的城市居民在周末也能开心。

我开始怀疑。很久以来,我一直想在布莱特买一个度假基地。我能不能...住在那里?

所以我问老板,如果我搬到维多利亚,他会不会介意。

答案是一个响亮的“是的,当然”。我仍然不太确定是否有人知道我要去维多利亚的哪里,但是我有一个肯定的答案,所以一切都在进行中。我在布莱特郊外的 Porepunkah 找到了一套有四间卧室和一间书房的房子,价格远低于我在悉尼的一间卧室。我租了辆卡车就走了。

【T2 Beautiful scenery

最初有一些困难,比如安装有线互联网(第一个月我在 4G 上工作),但一旦我安顿下来,我发现我更放松,更专注,总体上更快乐。生活更简单了,我的作息更有规律了,而且我每天都可以看山!

这花了一段时间,但最终我意识到在一个城市里,噪音主宰了你的生活。我花了一段时间才改掉到处戴降噪耳机的习惯——这个习惯我还没有完全改掉。出去喝杯咖啡或遛狗而不被交通噪音打扰,这是一种简单而不被重视的快乐。

我搬到布莱特已经九个月了,野马也不能把我拖出去。当然,在城市里你不会遇到一些小问题,但是每天看到山和树的价值意味着我可以等几天来修理我的冰箱。

Walking my dog along a beautiful stream Going for a mountain bike ride

我们不要自欺欺人了,在乡下修理你的冰箱可能需要几天时间。这不仅仅是老生常谈,这里的事情确实进展得更慢。我的镇上甚至没有送货上门的邮件。幸运的是,Octopus 的灵活性意味着我可以在一天中抽出时间来做一些需要做的小工作,并在以后弥补时间。有时候,我甚至可以挤出一个下午骑一两个小时的自行车,我发现这非常有用,因为山区冬天冰冷的早晨和黑暗的夜晚开始刺痛。

如果你是一个非常“随叫随到”的人,你重视城市生活提供的无限选择,你可能不会成功。但是,如果你能学会珍惜某种简单的生活,这可能正是你想要的。

使用 Flyway 和 Octopus 执行容器的数据库部署——Octopus Deploy

原文:https://octopus.com/blog/flyway-deployments-with-execution-containers

flyway logo inside execution container in front of laptop

我最近使用了 Flyway 和 Octopus Deploy 来部署数据库更改。Flyway 给我留下了深刻的印象,除了我必须将工具和 Java 运行时引擎(JRE)捆绑在一起,或者将它们预安装在一个 worker 上。

在 Octopus Deploy 的 2020.2 版本中,我们引入了执行容器特性。执行容器使用 Docker 映像来管理依赖性。

这篇文章介绍了如何使用执行容器和 Flyway 在 Octopus Deploy 中配置数据库部署。

执行容器基础

我不喜欢在我的包中包含所有运行 Flyway 的二进制文件,因为这会导致包膨胀。在我的例子中,差别是 10 KB 对 90 MB。

作为一名开发人员,我还负责升级二进制文件,并将它们包含在我的 Git repo 中。我还避免在员工身上预装工具,因为这意味着每个人都在同一版本上,升级可能会影响每个人。

执行容器通过使用 Docker 映像来管理依赖性,从而解决了这两个问题。Docker 映像拥有所有必要的工具(JRE、Flyway、PowerShell 等。)已安装。您可以指定部署过程中使用的 Docker 映像和标记。当部署使用执行容器运行时,Calamari 执行 Docker run 命令。此外,Calamari 会自动将文件夹挂载到容器中。

任务日志显示类似于以下内容的命令:

docker run --rm  --env TentacleHome=/home/Octopus  -w /home/Octopus/Work/20210329204922-325128-24   -v /home/Octopus/Work/20210329204922-325128-24:/home/Octopus/Work/20210329204922-325128-24  -v /home/Octopus:/home/Octopus  index.docker.io/octopuslabs/flyway-workertools:latest 

您拥有的任何包都会自动提取到/home/Octopus/Work/[DATETIME]文件夹中。这发生在幕后。要从直接在 worker 上运行改为在执行容器上运行,只需单击一个单选按钮并提供包名。其他都一样。

飞行路线执行容器

Octopus Deploy 提供了你可以使用的官方 Docker 图片。不幸的是,这些图像不能在这个例子中使用,原因有二:

  1. 它们包括几十种需要从 Docker Hub 下载千兆字节以上数据的工具。
  2. 没有一张图片包含飞行路线。

为了克服这一点,我创建了一个 Docker 图像,你可以在这个例子中使用。我还创建了一个 GitHub 动作,它将每天运行一次,并在检测到新版本时构建一个新映像。

这个 Docker 映像的基础映像包括我们支持的最流行的脚本语言:PowerShell 和 Python。基于 Ubuntu 的镜像也支持 Bash。

脚手架

需要完成两个脚手架步骤:

  1. 安装 Flyway 数据库迁移步骤模板
  2. 添加一个 Docker 容器注册表外部提要,并将其指向https://index.docker.io

external docker feed

Flyway 数据库迁移步骤模板

如果您在我们的社区步骤模板库中搜索Flyway,您会注意到许多 Flyway 步骤模板。 Flyway 数据库迁移步骤模板旨在取代旧的步骤模板。

这个新步骤模板的主要区别在于:

  1. 您可以选择任何命令(migrate、info、validate 等。)飞行路线支持。
  2. 我和 Redgate 的 Flyway 团队一起寻找流行的命令行开关。
  3. 支持免费和付费版本的 Flyway,使您能够进行模拟迁移,并能够使用 undo 命令。
  4. 它既可以在 Linux 上运行,也可以在 Windows 上运行。
  5. 它试图找到 Flyway 可执行文件,使得将 Flyway 包含在包中(如果您喜欢的话)或在执行容器中运行它变得容易。
  6. SQL 和 JAR 迁移都受支持。

我遵循了类似的模式,在最近的其他步骤模板中包含了更多的参数,例如:

我这样做是为了确保任何以-开头的参数都是 Flyway 命令行工具中的命令行开关

打包迁移脚本

我们的文档指导你构建你的包。然而,如果您只有 SQL 文件,就没有什么可构建的了。你只需要把文件夹打包到你的构建服务器上,然后推给 Octopus。

考虑这个例子: sample folder to package

您需要在构建服务器上的db/src文件夹中运行 Octo Pack 命令。该包将包含这些文件夹和内容。

sample package

在构建包之后,您需要将它发布到 Octopus Deploy。为了验证概念,您不需要构建服务器。可以使用 7-Zip 之类的工具将文件夹压缩成Flyway.Test.1.0.0.zip,手动上传包。这就是我为这篇文章所做的。

然而,在概念验证之后,如果集成一个构建服务器是有意义的,我们有文档和博客文章来帮助你。

构建服务器的示例:

配置项目

现在我们可以配置项目了,因为我们已经上传了步骤模板、Docker 提要和包。

首先,创建一个项目。在这个例子中,我将使用名称 Flyway - Azure SQL 执行容器。参见我们的 samples 实例中的 Flyway 示例,了解如何使用执行容器。

变量

创建项目后,导航至变量并添加必要的变量。

我推荐命名空间变量,例如项目变量使用Project.[Component].[VariableName],库变量集变量使用[VariableSetName].[Component].[VariableName]。这将使在流程中插入变量时更容易找到它们。

  • Project.Database.ConnectionString:我要部署到的数据库的连接字符串。请注意:这是我的例子使用 SQL Server 和将其改为 Oracle、MySQL、PostgreSQL、Maria、Snowflake 等之间唯一的真正的区别。
  • Project.Database.Name:要部署到的数据库的名称。
  • Project.Database.Password:进行部署的数据库用户的密码。
  • Project.Database.UserName:执行部署的数据库用户的用户名。
  • Project.Database.Server.Name:数据库所在服务器的名称。
  • Project.Flyway.LicenseKey:fly way 许可证密钥需要利用诸如模拟运行部署和撤消等功能。请注意:如果没有提供许可证密钥,Flyway 将恢复为社区版。
  • Project.Worker.Pool:工人池,工作将在这里完成。

The flyway project variables

部署流程

我们的文档中,我们建议从以下数据库部署流程开始:

  1. 生成一个 delta 脚本,并将其作为一个工件进行附加。
  2. 通知数据库管理员等待批准(仅在生产中)。
  3. 数据库管理员通过人工干预批准增量脚本(仅在生产中)。
  4. 部署数据库更改。
  5. 通知团队成功或失败。

overview of the deployment process

通知步骤可以是电子邮件、Slack、微软团队或您选择的任何工具。手动干预是不言自明的,我们在文档中包含了这些信息。

使用 Flyway 数据库迁移步骤模板

生成增量报告和部署数据库更改是使用 Flyway 数据库迁移步骤模板完成的。当您将其添加到流程中时,请进行以下更改:

  1. 更新步骤的名称。
  2. 把它改成在一个工人身上运行。
  3. 选择一个工作池。
  4. 将容器图像更改为runs inside a container, on a worker
  5. 输入octopuslabs/flyway-workertools:latest作为 Docker 图像;Docker 会根据主机运行的内容自动下载正确的架构(Ubuntu 或 Windows)。

工人必须安装 Docker 才能工作。章鱼云提供了已经在运行 Docker 的托管工人。如果你想自己托管你的工人,Linux 工人必须运行 Linux 容器,而 Windows 工人只能运行 Windows 容器。

configuring Flyway to run on the execution container

步骤模板将在哪里运行现在已经配置好了。接下来,我们配置参数:

  • 选择包含您希望 Flyway 运行的脚本的包。
  • 可选:输入飞行路线所在的路径。

如果你在octopuslabs/flyway-workertools执行容器上运行这个,你不需要输入任何东西。步骤模板将自动找到要运行的可执行文件。

  • 选择希望该步骤运行的命令。

common parameters

Octopus 中最常用的命令是:

  • info:这将生成一个列表,列出所有找到的脚本以及它们相对于正在部署的数据库的状态。当使用 community edition 时,info 命令是理想的,您需要列出将在数据库上运行的脚本。

using info to list pending scripts

  • migrate dry run:这将生成一个包含所有待定 SQL 脚本的文件,它将被保存为一个工件,DBA 可以下载和查看。这优于info,但仅当您提供一个 Flyway 许可密钥时才受支持。

migrate dry run file generated as an artifact

  • migrate:这将获取包中的所有 SQL 脚本和 JAR 文件,并在目标数据库上运行它们。这是执行实际工作的命令。

migrate command in action

接下来是许可参数,接下来是连接参数,最后是 Flyway 支持的各种命令行开关。

  • 因为我有许可证密钥,所以把版本改成了Teams
  • 下一个选项是传递许可证密钥。
  • 然后,使用连接字符串格式提供数据库的 URL。
  • 之后是可选的参数,供 Flyway 运行。

remaining parameters

step 模板包括每个参数的详细帮助文本以及相应文档的链接。

parameter help text

并非所有命令都支持所有命令行参数。步骤模板足够智能,可以排除命令不支持的参数。

生成增量脚本步骤

生成供 DBA 批准的增量脚本的步骤设置了以下参数。我有一个许可证密钥,所以我正在使用migrate dry run命令。如果我没有许可证密钥,我会选择info作为命令。

generate delta report step

部署数据库更改步骤

从包中部署数据库变更的步骤实际上与生成增量脚本步骤相同。唯一的区别是使用命令migrate而不是migrate dry run

deploy database changes step

结论

正如本文所展示的,更新您的流程以在执行容器中运行并不复杂,尤其是如果您使用的是 Octopus Cloud。添加 Docker 容器注册表外部提要后,只需为应该在执行容器上运行的每个步骤单击一个单选按钮。

这是一个很小的变化,但是它使部署过程和管道更加健壮。您可以利用运行您需要的 Flyway 的精确版本的工具,而无需所有维护开销。

愉快的部署!

Java CI/CD:从发布管理到运营——Octopus 部署

原文:https://octopus.com/blog/java-ci-cd-co/from-cd-to-co

Java CI/CD: From release management to operations

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

在之前的博文中,我们集成了 Jenkins 和 Octopus,在 Docker 映像被推送到 Docker Hub 后触发了对 Kubernetes 的部署。我们还在 Octopus 中添加了额外的环境来表示规范的开发➜测试➜生产进程。这给我们留下了一个在环境之间自动(如果不一定是自动的)发布管理的部署管道。

虽然传统的部署渠道以生产部署结束,但 Octopus 通过 runbooks 为 DevOps 生命周期的运营阶段提供了解决方案。通过使用 runbooks 自动执行数据库备份、日志收集和服务重启等常见任务,Jenkins 和 Octopus 的结合提供了一个完整的部署和运营管道,涵盖了应用程序的整个生命周期。

添加数据库

在为数据库备份创建操作手册之前,我们需要一个数据库。

您通常会在生产环境中使用 RDS 之类的托管服务。RDS 提供了现成的高可用性、备份、维护窗口、安全性等,所有这些都需要花费大量精力来使用本地数据库进行复制。然而,出于本博客的演示目的,我们将把 MySQL 部署到 EKS,并将我们的 PetClinic 应用程序指向它。然后,我们可以针对数据库编写常见管理任务的脚本,以演示保持生产部署运行的连续操作。

我们将使用官方的 MySQL Docker 映像,但是我们还需要一些额外的工具来将备份转移到第二个位置。因为我们使用 AWS 来托管我们的 Kubernetes 集群,所以我们将把数据库备份到 S3。这意味着我们需要 MySQL Docker 映像中包含的 AWS CLI 来传输数据库备份。

向图像添加新工具很容易。我们获取基本的 mysql 映像,并运行安装 AWS CLI 所需的命令。我们的 Dockerfile 看起来像这样:

FROM mysql
RUN apt-get update; apt-get install python python-pip -y
RUN pip install awscli 

然后,我们构建这个映像,将其推送到 Docker Hub,并使用下面的 Jenkinsfile 在 Octopus 中创建和部署一个版本。您会注意到,这个Jenkinsfile几乎是前一个的一个精确副本,对 Docker 映像和部署的 Octopus 项目的名称进行了更改:

pipeline {
    agent {
        label 'docker'
    }
    //  parameters here provide the shared values used with each of the Octopus pipeline steps.
    parameters {
        // The space ID that we will be working with. The default space is typically Spaces-1.
        string(defaultValue: 'Spaces-1', description: '', name: 'SpaceId', trim: true)
        // The Octopus project we will be deploying.
        string(defaultValue: 'MySQL', description: '', name: 'ProjectName', trim: true)
        // The environment we will be deploying to.
        string(defaultValue: 'Dev', description: '', name: 'EnvironmentName', trim: true)
        // The name of the Octopus instance in Jenkins that we will be working with. This is set in:
        // Manage Jenkins -> Configure System -> Octopus Deploy Plugin
        string(defaultValue: 'Octopus', description: '', name: 'ServerId', trim: true)
    }
    stages {
        stage ('Add tools') {
            steps {
                tool('OctoCLI')
            }
        }
        stage('Building our image') {
            steps {
                script {
                    dockerImage = docker.build "mcasperson/mysqlwithawscli:$BUILD_NUMBER"
                }
            }
        }
        stage('Deploy our image') {
            steps {
                script {
                    // Assume the Docker Hub registry by passing an empty string as the first parameter
                    docker.withRegistry('' , 'dockerhub') {
                        dockerImage.push()
                    }
                }
            }
        }
        stage('deploy') {
            steps {                                
                octopusCreateRelease deployThisRelease: true, environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', waitForDeployment: true                
            }
        }
    }
} 

MySQL Kubernetes 部署 YAML 也与我们的之前的示例非常相似,使用了新的映像名称,并添加了两个环境变量来配置数据库凭证和创建初始数据库:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    spec:
      containers:
        - name: mysql
          image: mcasperson/mysqlwithawscli
          ports:
            - name: sql
              containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: Password01!
            - name: MYSQL_DATABASE
              value: petclinic 

因为我们不需要公开访问数据库,所以我们使用集群 IP 服务公开 MySQL 实例,这允许其他 pods 访问数据库,但不会创建公共负载平衡器:

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  type: ClusterIP
  ports:
    - name: sql
      port: 3306
      protocol: TCP 

部署由上面的 YAML 创建的资源会导致可以使用主机名mysql从集群中的其他 pods 访问 MySQL 实例。

为了配置 PetClinic 使用 MySQL 数据库,我们需要定义四个环境变量。这些变量用于配置应用程序-mysql.properties 配置文件中的设置:

  • MYSQL_URL,这是 MySQL 数据库的 JDBC URL。
  • MYSQL_USER,作为 MySQL 用户连接,设置为root
  • MYSQL_PASS,这是 MySQL 密码,设置为我们在 MySQL pod 上的MYSQL_ROOT_PASSWORD环境变量中定义的密码。
  • SPRING_PROFILES_ACTIVE,它定义了 Spring 将用来配置应用程序的概要文件,设置为 mysql 来加载应用程序-mysql.properties 配置文件。

我们新 PetClinic 部署的 YAML 如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: petclinic
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: petclinic
          image: mcasperson/petclinic
          ports:
            - name: web
              containerPort: 8080
          env:
            - name: MYSQL_URL
              value: 'jdbc:mysql://mysql/petclinic'
            - name: SPRING_PROFILES_ACTIVE
              value: mysql
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PASS
              value: Password01! 

我们现在有了一个 MySQL 数据库,并配置了 PetClinic 将其用作数据存储。

备份数据库

在开发运维生命周期的持续运营阶段,最明显的任务之一可能就是备份数据库。

MySQL Docker image 文档提供了一个使用mysqldump在活动容器内运行docker exe来备份数据库的示例命令。我们将以那个例子为例,将其重写为对kubectl exe的调用,以在一个正在运行的 pod 上执行备份。

下面的 PowerShell 脚本找到了 MySQL pod 的名称(由于该 pod 是作为部署的一部分创建的,所以它的名称是随机的),调用mysqldump创建数据库的备份,然后调用aws s3 cp将备份上传到 S3:

# Get the list of pods in JSON format
kubectl get pods -o json |
# Convert the output to an object
ConvertFrom-Json |
# Get the items property
Select -ExpandProperty items |
# Limit the items to those with the name starting with "mysql"
? {$_.metadata.name -like "mysql*"} |
# We only expect to find 1 such pod
Select -First 1 |
# Execute mysqldump on the pod to perform a backup
% {
    Write-Host "Performing backup on pod $($_.metadata.name)"
    kubectl exec $_.metadata.name -- /bin/sh -c 'mysqldump -u root -p#{MySQL Password} petclinic > /tmp/dump.sql 2> /dev/null'
    kubectl exec $_.metadata.name -- /bin/sh -c 'AWS_DEFAULT_REGION=us-east-1 AWS_ACCESS_KEY_ID=#{AWS.AccessKey} AWS_SECRET_ACCESS_KEY=#{AWS.SecretKey} aws s3 cp /tmp/dump.sql s3://mattc-deployment-backup/dump.sql'    
} 

该脚本在添加到 runbook 的运行 kubectl CLI 脚本步骤中执行:

The kubectl script performing the database backup 执行数据库备份的 kubectl 脚本。

The result of executing the database backup 执行数据库备份的结果。

我们不想手动备份数据库,所以 Octopus 允许安排 runbooks。这里我们有一个执行每日备份的触发器:

A scheduled backup 定时备份。

虽然找到执行备份的 pod 的名称需要一些处理,但是这个脚本并不特别复杂,经验丰富的系统管理员无疑见过比这复杂得多的管理脚本。按计划运行脚本的能力也不是什么突破性的东西。

当您考虑到在应用程序的生命周期中需要与这个基础设施进行交互的不同团队时,这种方法的真正优势就变得很明显了。

因为 Octopus 已经部署到我们的基础设施中,所以我们不需要复制凭证或其他设置(如 URL)来管理基础设施。都已经在章鱼里了。

操作手册消除了对额外工具和配置设置的需要,否则这些工具和配置设置可能需要在专门的支持笔记本电脑上维护,这意味着电话支持人员只需点击一个按钮,就可以从网络浏览器(如有必要,在他们的电话上)执行这些操作手册。因为这些操作手册的执行是在审计日志中捕获的,并且这些步骤的输出是在操作手册运行的历史中捕获的,所以在发现问题的根本原因时,您不会遇到同样的困难,如果运营团队必须从他们自己的工作站运行临时脚本,您就会遇到同样的困难。

一个额外的好处是,run book 知道我们的多种环境,所以正如我们的应用程序代码必须通过多种环境才能被认为已准备好用于生产发布,我们的 run book 也可以在非生产环境中进行测试和验证,以确保它们在生产中可以被信任。

所有这一切意味着支持生产系统所需的业务知识现在可以在可测试和可重复的操作手册中获取,使得支持移交更加容易,因为所有团队都共享相同的工具箱。

当保存在操作手册中时,这十几行 PowerShell 代表了一个共享的、可验证的、可审计的、易于访问的集中式业务知识单元,旨在保持您的系统以最佳状态运行。

重启 pod

让我们看另一个例子,这次是重新启动 PetClinic 应用程序。

下面的脚本找到名称以 petclinic 开头的 pod 并删除它们。因为这些 pod 是由 Kubernetes 部署创建的,所以它们将被自动重新创建,实质上是执行 pod 重启:

# Get the list of pods in JSON format
kubectl get pods -o json |
# Convert the output to an object
ConvertFrom-Json |
# Get the items property
Select -ExpandProperty items |
# Limit the items to those with the name starting with "mysql"
? {$_.metadata.name -like "petclinic*"} |
# Delete the pod to have the deployment recreate it
% { kubectl delete pod $_.metadata.name} 

如果你不熟悉 Kubernetes,像kubectl delete这样的命令可能会让人望而生畏。碰巧的是,由于我们的应用程序的部署方式,该操作将重启 pod,而不是永久删除它们。但是 DevOps 团队的新成员如何知道这个命令是安全的呢?

通过向运行手册添加描述,我们可以提供运行手册何时何地可以运行的指导。在下面的截图中,您可以看到对 Restart PetClinic runbook 的描述,它清楚地表明这是可以在生产中运行的东西:

Runbooks with a description 带有描述的运行手册,帮助运营团队了解何时何地运行它们。

更进一步,我们可以使用 Octopus 中的权限来限制对运行手册的访问,这可能需要更深入地了解基础架构才能安全运行,或者在采取任何行动之前使用手动干预来获得批准。

同样,这是一个将业务知识封装在操作手册中以减轻基础设施支持负担的例子。

结论

传统的部署管道以部署结束,但实际上,部署之后发生的事情与部署本身一样重要。这就是连续作战思想的由来。Runbooks 为您的团队提供了从第一次代码提交到产品部署后数周、数月或数年支持应用程序所需的工具。因为 Octopus 已经了解了您的基础架构以及如何部署到基础架构,所以 runbooks 可以轻松利用现有的凭证、目标和环境来实现 DevOps 生命周期的运营阶段。

基本上,操作手册将保持部署运行的脚本和工作流本身视为一种有价值的产品。从持续交付中获取最佳实践,并将其扩展到运营任务中,可确保整个应用生命周期由您的开发运维团队以连贯的方式进行管理。

通过这篇博文,我们结束了从本地构建的遗留 Java 应用程序到集成了 Jenkins、Octopus、Docker 和 AWS EKS 的完整部署管道的旅程。我希望示例管道为在您的组织中实现持续集成(CI)、发布管理和持续操作提供了一个基础。

浏览 DevOps 工程师手册了解更多关于 DevOps 和持续交付的信息。

愉快的部署!

Java CI/CD:从持续集成到发布管理- Octopus Deploy

原文:https://octopus.com/blog/java-ci-cd-co/from-ci-to-cd

Java CI/CD: From Continuous Integration to release management

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

在之前的博文中,我们使用 EKS 在 AWS 中使用 Octopus 构建了一个 Kubernetes 集群,然后将 Jenkins 创建的 Docker 映像部署为 Kubernetes 部署和服务。

然而,我们仍然没有一个完整的部署管道解决方案,因为 Jenkins 没有与 Octopus 集成,让我们手动协调构建和部署。

在这篇博文中,我们将扩展我们的 Jenkins 构建来调用 Octopus,并在 Docker 映像被推送到 Docker Hub 时启动部署。我们还将创建额外的环境,并管理从本地开发环境到最终生产环境的发布。

安装 Jenkins 插件

Octopus 为 Jenkins 提供了一个插件,该插件公开了自由式项目和管道脚本中的集成步骤。通过导航到 管理詹金斯➜管理插件 安装此插件。从这里,你可以搜索“章鱼”并安装插件。

Octopus 插件使用 Octopus CLI 与 Octopus 服务器集成。我们可以在代理上手动安装 CLI,但在本例中,我们将使用定制工具插件下载 Octopus CLI 并将其推送到代理:

Install the custom tools plugin 安装自定义工具插件。

我们添加 Octopus 服务器,我们的管道将连接到,通过导航到 管理詹金斯➜配置系统 :

Define the Octopus Server 定义八达通服务器。

然后我们需要在 管理詹金斯➜全球工具配置 下定义一个定制工具。自定义工具的名称为Octopus CLI,因为在我的例子中代理运行在 Windows 上,Octopus CLI 将从https://download . Octopus deploy . com/Octopus-tools/7 . 4 . 1/Octopus tools . 7 . 4 . 1 . win-x64 . zip下载。对于最新版本的 CLI,以及支持其他操作系统的二进制文件,请参见 Octopus 下载页面:

Define the Octopus CLI custom tool 定义 Octopus CLI 自定义工具。

全局工具配置页面上,我们定义了 Octopus CLI 的路径。自定义工具插件将 Octopus CLI 安装到目录<jenkins home>/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/OctoCLI,其中<jenkins home>是 Jenkins 服务器或执行构建的代理的主目录。在我的例子中,代理主目录是C:\JenkinsAgent,所以从C:\JenkinsAgent\tools\com.cloudbees.jenkins.plugins.customtools.CustomTool\OctoCLI\octo开始就可以使用 Octopus CLI。刀具名称保留为默认:

Define the Octopus CLI path 定义 Octopus CLI 路径。

通过配置这些工具,我们可以在 Docker 映像被推送到 Docker Hub 之后,更新管道脚本来启动 Octopus 中的部署。

更新詹金斯管道

我们现有的管道被配置为构建 Docker 映像并将其推送到 Docker Hub。我们将保留这些步骤,并添加额外的步骤来安装作为定制工具的 Octopus CLI,然后在 Docker 映像被推送后在 Octopus 中创建和部署一个版本。让我们看看完整的管道:

pipeline {
    agent {
        label 'docker'
    }
    parameters {
        string(defaultValue: 'Spaces-1', description: '', name: 'SpaceId', trim: true)
        string(defaultValue: 'Petclinic', description: '', name: 'ProjectName', trim: true)
        string(defaultValue: 'Dev', description: '', name: 'EnvironmentName', trim: true)
        string(defaultValue: 'Octopus', description: '', name: 'ServerId', trim: true)
    }
    stages {
        stage ('Add tools') {
            steps {
                tool('OctoCLI')
            }
        }
        stage('Building our image') {
            steps {
                script {
                    dockerImage = docker.build "mcasperson/petclinic:$BUILD_NUMBER"
                }
            }
        }
        stage('Deploy our image') {
            steps {
                script {
                    // Assume the Docker Hub registry by passing an empty string as the first parameter
                    docker.withRegistry('' , 'dockerhub') {
                        dockerImage.push()
                    }
                }
            }
        }
        stage('deploy') {
            steps {                                
                octopusCreateRelease deployThisRelease: true, environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', waitForDeployment: true                
            }
        }
    }
} 

这个管道有一些新的设置来支持与 Octopus 的集成。

我们从定义公共参数开始。当我们在 Octopus 中创建和部署一个版本时,将会引用这些参数,它们提供了一种很好的方法来将 Octopus 细节从任何特定的实例中分离出来,同时还提供了合理的默认值:

 parameters {
        string(defaultValue: 'Spaces-1', description: '', name: 'SpaceId', trim: true)
        string(defaultValue: 'Petclinic', description: '', name: 'ProjectName', trim: true)
        string(defaultValue: 'Dev', description: '', name: 'EnvironmentName', trim: true)
        string(defaultValue: 'Octopus', description: '', name: 'ServerId', trim: true)
    } 

为了让自定义工具插件提取代理主目录中的 Octopus CLI,我们需要调用tool('OctoCLI'):

 stage ('Add tools') {
            steps {
                tool('OctoCLI')
            }
        } 

最后一个阶段调用octopusCreateRelease来创建一个发布并将其部署到 Octopus 中的第一个环境。默认情况下,Octopus 将使用部署步骤中引用的最新版本的包来创建部署,这意味着我们将部署 Jenkins 在前一阶段上传到 Docker Hub 的 Docker 映像:

 stage('deploy') {
            steps {                                
                octopusCreateRelease deployThisRelease: true, environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', waitForDeployment: true                
            }
        } 

通过对管道的这些更改,我们在 Jenkins 中重新运行了该项目,从控制台日志中,我们可以看到 Jenkins 已经成功地触发了 Octopus 中的部署:

Jenkins project build logs showing the Octopus deployment output * Jenkins 项目构建日志显示了 Octopus 部署输出。*

以下是 Octopus 中相应的部署:

The Octopus deployment 章鱼部署。

持续部署与持续交付

多年来,首字母缩略词 CI/CD 的 CD 半已经确定了两个定义:

  • 连续部署,这意味着一个完全自动的部署管道,假设所有测试和其他自动化需求都得到满足,那么每次提交都会进入生产环境。
  • 连续交付,这意味着每个提交可以通过自动化的,但不一定是自动的,部署管道进入生产。通过环境提升(或不通过环境提升)的决定仍然是由人做出的。

虽然连续部署,就其定义而言,消除了部署过程中的所有摩擦,但是有许多有效的理由来实现连续交付。例如,您可能需要与其他团队协调部署,产品负责人可能需要签署新功能,法规要求可能要求开发人员在没有一些审查过程的情况下不得修改生产基础结构,或者您可能只想保留在发布进入生产之前手动测试和验证发布的能力。

如果您阅读了关于 CI/CD 最佳实践的博客文章,您可能会留下这样的印象,即持续部署是您必须努力实现的事情。虽然允许真正的连续部署管道的实践将会有价值,但是我们交谈过的大多数开发团队都报告说连续交付对他们有效。

对于这个博客,我们将创建一个连续的交付管道,通过 Octopus 仪表板管理向多个环境的发布。

添加环境

我们在 Octopus 中只有一个环境叫做 Dev 。但是,典型的工作流会在生产过程中通过多个环境促进部署。为了实现这一点,我们需要在 Octopus 中创建更多的环境,我们称之为测试生产:

Add the Test and Prod environments 添加测试和生产环境。

我们需要确保我们的 Kubernetes 目标也在这些新环境中:

Add the Kubernetes target to the new environments 将 Kubernetes 目标添加到新环境中。

我们现在能够通过 Octopus 仪表板将版本从开发环境升级到测试环境:

The Octopus dashboard showing the next environment to deploy to 显示下一个部署环境的 Octopus 仪表盘。

将发布升级到测试环境,我们可以看到我们的 Kubernetes 资源正在 petclinic-test 名称空间中创建。如果您还记得上一篇博文,我们配置了 Kubernetes 步骤来部署到一个名为pet clinic-# { Octopus }的名称空间。Environment.Name | ToLower} ,这就是为什么新环境的部署被放置在新的名称空间中:

A deployment to the Test environment 一个部署到测试环境中。

为了证明这一点,我们可以在测试环境中重新运行 runbook Get 服务。我们可以看到,已经为新的服务资源创建了一个新的负载平衡器主机名:

The details of the load balancer service created in the Test environment 在测试环境中创建的负载平衡器服务的详细信息。

这样,我们就有了完整的部署渠道。

结论

在这篇文章中,在 Jenkins 完成 Docker 映像的构建和推送之后,我们在 Octopus 中触发了一个部署。这意味着我们已经实现了与 Jenkins 的持续集成,测试、构建和发布 Docker 映像,以及与 Octopus 的持续交付,提供了到开发环境的自动部署,以及准备在其他环境中手动触发的自动化流程。

现在,我们只需点击几个简单的按钮,就可以将应用程序源代码转化为产品。那些负责发布管理的人除了 web 浏览器之外不需要任何特殊的工具。每个构建和部署都在 Jenkins 和 Octopus 仪表板中被跟踪、审计和总结。

但是那些看到他们的代码放在客户手中的人知道,虽然没有什么比生产部署的前 10 分钟更能激发信心,但接下来的几个小时和几天是艰难的。需要管理数据库备份,需要安排操作系统更新,需要收集日志来诊断支持问题,并且需要执行一些好的、老式的开关操作。

在下一篇博文中,我们将展示在运行手册中实现的这些维护过程的例子,以完成我们管道的最后阶段:运营。

浏览 DevOps 工程师手册了解有关 DevOps 和 CI/CD 的更多信息。

Java CI/CD:从持续集成到 Kubernetes 部署——Octopus 部署

原文:https://octopus.com/blog/java-ci-cd-co/from-ci-to-cloud

Java CI/CD: From Continuous Integration to Kubernetes deployments

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

在之前的博文中,我们用 Jenkins 配置了一个 CI 服务器,它提供了一个中心位置来构建和发布我们的 Docker 映像。该映像现在可以从 Docker Hub 公开获得,下一步是创建可以托管我们的 Docker 容器的基础设施。

在本文中,我们将从 Octopus 的 AWS 中创建一个弹性 Kubernetes 服务(EKS)实例。

获取 Octopus 云实例

我们将使用 Octopus 编写 EKS 星团的创建脚本。获得 Octopus 的最简单方法是注册一个免费的 Octopus Cloud 实例。启动并运行一个实例只需要几分钟时间。

第一步是在 Octopus 中创建一个 AWS 帐户,该帐户将用于创建并连接到 EKS 集群。AWS 帐户由帐户密钥和密钥组成:

An example AWS account 一个 AWS 账户的例子。

名为eks CTL-Create Cluster(bash)的社区步骤可以添加到 runbook 中,以便在 Octopus 中快速创建 EKS 集群和相关的 Kubernetes 目标。该脚本执行 EKS CLI 工具来创建 EKS 集群:

The community step to create an EKS cluster 社区一步创建一个 EKS 集群。

为了方便使用 ekscli 工具,Octopus 支持基于图像在 Docker 容器内运行一个步骤,并且为图像提供了广泛的常用云工具选择,包括 ekscli

要使用 Octopus Cloud 中的 Docker 映像,我们需要选择 Ubuntu 动态工作器:

Ubuntu dynamic workers

为了使用工人工具图像,我们在容器图像部分选择它:

Selecting the worker tools image

用要创建的 EKS 集群的详细信息填充该步骤。对于本例,下面的 eksctl 配置 YAML 创建了一个包含两个 t3a.small 节点的集群:

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: k8s-cluster
  region: us-east-1

nodeGroups:
  - name: ng-1
    instanceType: t3a.small
    desiredCapacity: 2
    volumeSize: 80 

The populated step 人口密集的一步。

如果在us-east-1中出现UnsupportedAvailabilityZoneException错误,请尝试另一个可用区域。eksctl 文档指出us-east-1容易出现这种错误。

在执行 runbook 之前,我们需要允许将动态创建的目标放置在目标环境中,在本例中称为 Dev 。启用此设置允许脚本步骤(如我们刚刚配置的社区步骤模板)创建 Octopus 目标:

Enabling dynamic infrastructure in the Dev environment 在开发环境中启用动态基础设施。

现在执行操作手册。当 runbook 完成时,一个新的 Kubernetes 目标被创建,我们可以将我们的应用程序部署到:

The Octopus Kubernetes target 章鱼目标。

The EKS clusterEKS 集群。

创建 Docker 提要

为了使用 Docker 图像,我们需要在 Octopus 中创建一个 Docker 提要。这个提要指向 https://index.docker.io ,这是 Docker Hub 注册表的 URL:

The Docker Hub feed in Octopus 将 Docker 集线器馈入章鱼。

然后我们可以测试提要,以确保 Octopus 可以找到我们的 Petclinic 图像:

Test the Docker feed 测试对接器进给。

将映像部署到 Kubernetes

现在,我们已经完成了部署到 Kubernetes 集群的所有配置。我们将使用部署 Kubernetes 容器步骤来配置 Kubernetes 部署资源,并通过负载平衡器服务公开它:

The Deploy Kubernetes containers step 部署 Kubernetes 容器的步骤。

您可以通过两种方式与此步骤进行交互。

第一种方法是使用 UI 来构建 Kubernetes 部署。当您不太熟悉 Kubernetes YAML 酒店时,这很方便,因为您可以通过专用的表单字段构建资源。

使用该步骤的第二种方法是通过点击编辑 YAML 按钮时显示的 YAML 表示法来编辑值:

The Edit YAML section 编辑 YAML 章节。

然后,您可以直接以 YAML 的身份编辑部署资源,这很方便,因为复制和粘贴现有的 YAML 只需一个操作就可以填充该步骤。通过将下面的 YAML 粘贴到文本框中,我们创建了一个引用 Docker 映像的部署:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: petclinic
spec:
  selector:
    matchLabels:
      octopusexport: OctopusExport
  replicas: 1
  strategy:
    type: RollingUpdate
    rollingUpdate: {}
  template:
    metadata:
      labels:
        octopusexport: OctopusExport
    spec:
      containers:
        - name: petclinic
          image: mcasperson/petclinic
          ports:
            - name: web
              containerPort: 8080 

Editing the step via YAML 通过 YAML 编辑步骤。

我们希望将我们的部署放在每个环境的单独名称空间中。这是通过将名称空间字段设置为 petclinic-#{Octopus 来实现的。Environment.Name | ToLower} :

以与配置部署相同的方式,我们可以用下面的 YAML 填充服务的详细信息。这个 YAML 创建一个负载平衡器服务,这将导致创建一个弹性负载平衡器(ELB)来公开部署。这个 ELB 有一个公共主机名,我们可以从我们的 web 浏览器访问:

apiVersion: v1
kind: Service
metadata:
  name: petclinic
spec:
  type: LoadBalancer
  ports:
    - name: web
      port: 80
      targetPort: 8080
      protocol: TCP 

The service YAML 为 YAML 服务。

有了这些设置,我们就可以部署到 EKS 集群了。日志显示 Kubernetes 部署和服务资源已成功创建:

PetClinic has been successfully deployed * PetClinic 已成功部署。*

所以现在唯一的问题是我们如何访问应用程序?

查询群集

我们将经常需要查询集群,以找到我们需要的信息,或者调试一个问题。通常,设置部署的人会在本地配置 kubectl,并使用特定命令快速查询集群的状态。

虽然这是可行的,并且有时确实是必要的,但是执行这样的特别命令忽略了这样一个事实,即如果这些命令是成功完成初始部署所必需的,那么它们也可能是解决未来部署问题所必需的。

查找我们刚刚创建的负载平衡器的主机名就是一个很好的例子。我们可以通过多种方式获取这些信息,要么从 AWS 控制台获取,要么调用 kubectl。然而,为了在我们完成后维护这个集群的人的利益,我们将通过另一个操作手册找到这个信息。

为了获得服务信息,使用名为Kubernetes-Inspect Resources的社区步骤模板创建一个 runbook:

The Kubernetes - Inspect Resources community step templateKubernetes-Inspect 资源社区步骤模板。

配置从 petclinic-#{Octopus 获取服务资源的步骤。Environment.Name | ToLower} 命名空间:

Getting the service details 获取服务详情。

运行 runbook 将代表我们用 kubectl 查询集群,在响应中显示负载平衡器的主机名:

The service details 服务详情。

这个过程比直接跳到控制台并运行 kubectl 稍不方便,但好处是我们已经启动了一个 runbook 库,其中包含了我们知道对使用我们的集群有用的步骤。这个库将非常有价值,因为我们希望将对这个基础设施的支持交给另一个团队。当您考虑到下一个团队只需要对 Octopus 的适当访问,而不需要 kubectl 或任何凭证时,这一点尤其正确,当您的寻呼机在凌晨 3 点响起时,这是受欢迎的。

现在我们知道了 ELB 的主机名,我们可以访问我们公开托管的应用程序:

PetClinic live and public * PetClinic live 和 public。*

结论

在本文中,我们使用 EKS 服务在 AWS 中创建了一个 Kubernetes 集群,并通过 Octopus 将我们的 PetClinic 应用程序部署到其中。我们还致力于通过 runbooks 调试集群,这提供了一个小但重要的基础,我们可以将它传递给在我们继续前进后最终负责该集群的团队。

我们还没有实现连续部署,因为 Jenkins 和 Octopus 之间没有集成。在下一篇文章的中,我们将连接我们的管道,以实现一个完整的 CI/CD 管道。

Java CI/CD:从 JAR 到 Docker——Octopus 部署

原文:https://octopus.com/blog/java-ci-cd-co/from-jar-to-docker

Java CI/CD: From JAR to Docker

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

也许没有哪个公共项目比 Spring PetClinic 更好地展示了一个长寿的 Java 应用程序。它诞生于 21 世纪初,至今仍自豪地出现在 Spring 网站上。

我们的 DevOps 生命周期之旅从本地工作站上 PetClinic 的本地构建开始。在这篇博客文章的结尾,我们已经用 Docker 封装了这个应用程序,提供了一个可重复的构建和执行环境

从本地构建开始

首先,我们克隆 PetClinic GIT 存储库,并使用与应用程序源代码一起签入的 Maven 包装器来构建和运行本地 JAR 文件:

git clone https://github.com/spring-projects/spring-petclinic.git
cd spring-petclinic
./mvnw spring-boot:run 

这个初始构建需要一些时间,因为 Maven 下载了组成应用程序的各种 Spring 库。幸运的是,这些库都被缓存了,所以后续的构建会完成得更快。

要查看该应用程序,请打开 http://localhost:8080:

PetClinic running locally * PetClinic 本地运行。*

要创建可分发的 JAR 文件,请运行以下命令:

./mvnw package 

这会运行单元测试,然后在target目录下创建一个独立的 JAR 文件,类似于petclinic.2.3.1.BUILD-SNAPSHOT.jar。我们可以使用以下命令运行该文件:

java -jar .\target\petclinic.2.3.1.BUILD-SNAPSHOT.jar 

这个本地测试、构建和运行的过程是每个应用程序的起点。

公平地说,PetClinic 实现了许多特性来使这些构建可重复,并且结果易于分发。mvnw脚本是 Maven 包装器,它提供了跨平台的脚本,如果本地机器没有安装 Maven 的适当版本,这些脚本可以被签入源代码控制。然后,Spring boot 创建易于版本化、复制和部署的自包含 JAR 文件。

然而,您仍然需要 Java 开发工具包(JDK)来构建应用程序,并且需要 JDK 或 Java 运行时环境(JRE)来运行它。PetClinic 依赖于一个相当老的 Java 版本,鉴于每六个月就会发布一个新的 Java 版本,不难想象开发人员必须修改 Java 安装来执行本地构建。

为了提供一个真正自包含的构建和执行环境,我们将把这个应用程序迁移到 Docker。

用 Docker 进行独立构建和执行

Docker 的主要特性之一是它能够将整个应用程序生态系统捆绑在一个自包含的映像中,该映像可以在一个隔离的环境中运行。对我们来说,这意味着我们可以用所需版本的 Java 和我们编译的应用程序构建和分发 Docker 映像,任何安装了 Docker 的人都可以运行它。

PetClinic repo 的一个分支已经用下面的代码在 GitHub 中创建,以便于访问。

Docker 映像由名为Dockerfile的文件中列出的步骤定义。我们的Dockerfile的内容如下所示:

FROM maven:3.6.3-jdk-8 AS build-env
WORKDIR /app

COPY pom.xml ./
RUN mvn dependency:go-offline
RUN mvn spring-javaformat:help

COPY . ./
RUN mvn spring-javaformat:apply
RUN mvn package -DfinalName=petclinic

FROM openjdk:8-jre-alpine
EXPOSE 8080
WORKDIR /app

COPY --from=build-env /app/target/petclinic.jar ./petclinic.jar
CMD ["/usr/bin/java", "-jar", "/app/petclinic.jar"] 

这个Dockerfile利用了一个叫做多阶段构建的特性。这允许我们创建一个更小的最终 Docker 映像来发布,因为它不包含构建应用程序所需的工具。

我们的构建基于 Maven 团队提供的现有 Docker 映像。这个映像预装了 JDK 和 Maven 工具:

FROM maven:3.6.3-jdk-8 AS build-env 

然后我们创建并移入一个名为/app的目录:

WORKDIR /app 

Maven pom.xml文件被复制到/app目录中:

COPY pom.xml ./ 

然后我们运行 Maven dependency:go-offline目标,它下载了构建应用程序所需的大多数库和 Maven 插件。

由于 Docker 缓存构建的方式,只要pom.xml文件没有改变,该映像的任何后续重建都将重用 Maven 的这次执行所执行的下载。对于 PetClinic 应用程序,这可以节省几分钟时间和数百兆字节:

RUN mvn dependency:go-offline 

Spring 包括一个源代码格式化工具,可以确保所有代码都有一致的风格。我们将调用 help 函数来确保 Maven 下载插件,这意味着 Docker 将依次缓存下载内容。这将为我们节省一次下载和随后的 Docker 映像重建:

RUN mvn spring-javaformat:help 

我们现在可以复制应用程序源代码的其余部分。Docker 检测被复制的源代码何时发生了变化,并从这一步重新运行映像构建过程来捕获变化。但是,所有的应用程序依赖项都已被缓存,因此从这一步开始的构建过程会相对较快:

COPY . ./ 

将源代码从 Windows 工作站复制到 Linux Docker 映像通常会导致格式化插件抱怨行尾。在这里,我们运行格式插件来自动修复复制文件的任何问题:

RUN mvn spring-javaformat:apply 

我们现在可以以 Maven package为目标构建应用程序。注意,我们还将变量finalName设置为petclinic。这将覆盖默认文件名petclinic.2.3.1.BUILD-SNAPSHOT.jar,生成一个名为petclinic.jar的一致文件:

RUN mvn package -DfinalName=petclinic 

我们的应用程序现在已经构建好了,我们进入多阶段构建的下一个阶段,生成我们想要分发的 Docker 映像。这个图像基于 OpenJDK JRE。

JRE 可以运行已编译的应用程序,但不包括编译应用程序所需的工具。这减小了最终图像的尺寸。

FROM openjdk:8-jre-alpine 

我们公开端口8080,这是我们的 Spring 应用程序监听的端口:

EXPOSE 8080 

我们创建并移入一个名为/app的目录:

WORKDIR /app 

在前一阶段编译的 JAR 文件被复制到当前映像中:

COPY --from=build-env /app/target/petclinic.jar ./petclinic.jar 

然后,我们指示映像在运行时执行 JAR 文件:

CMD ["/usr/bin/java", "-jar", "/app/petclinic.jar"] 

要构建 Docker 映像,请运行以下命令:

docker build . -t petclinic 

这将构建 Docker 映像,并给它分配标签petclinic:latest。请注意,如果没有指定其他标签,默认情况下会应用latest

最后,使用以下命令运行 Docker 映像:

docker run petclinic 

和以前一样,这个应用程序可以在 http://localhost:8080 上找到。

我们现在有了一个Dockerfile,它包含了构建和运行我们的应用程序所需的所有步骤。现在,除了 Docker 之外,无需任何其他工具,就可以从源代码构建这个应用程序。我们现在有了一个真正独立的构建过程。

分发 Docker 图像

Docker 图像可以与许多 Docker 注册中心在线共享。最受欢迎的是 Docker Hub ,它为托管公开可用的 Docker 图像提供免费账户。

为了共享 PetClinic Docker 图像,我们需要注册一个 Docker Hub 帐户。我的账号叫mcasperson

创建帐户后,使用命令docker login登录 Docker Hub:

docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: mcasperson
Password:
Login Succeeded 

要分享图片,需要用你的用户名进行标记。在我的例子中,我需要构建一个名为mcasperson/petclinic的映像(其中mcasperson是我的 Docker Hub 用户名):

docker build . -t mcasperson/petclinic 

构建应该会很快完成,因为没有文件被更改,所有步骤都被缓存。

要上传图像,运行以下命令,用您的 Docker Hub 用户名替换mcasperson:

docker push mcasperson/petclinic 

这张照片现在被分享到网上,任何人都可以访问。

要运行公共映像,请执行以下命令:

docker run -p 8080:8080 mcasperson/petclinic 

如果本地没有映像,Docker 将下载它,然后像我们之前做的那样运行它。选项-p 8080:8080明确地将本地端口 8080 映射到容器端口 8080。

结论

在本文中,我们采用了一个典型的 Java 应用程序,并将其封装为 Docker 映像。这张图片被上传到 Docker 注册处,供公众使用。

通过这些改变,我们创建了一个可重复的构建和执行过程,任何人只需安装 Docker 就可以使用。如果我们切换到较新版本的 Java,甚至完全切换语言,应用程序仍然可以用相同的 Docker 命令构建和运行。

虽然 Docker 可以方便地封装构建过程,但不能保证源代码编译成功或测试全部通过。随着越来越多的开发人员开始开发一个应用程序,代码库的健康状况需要由一个中心真实来源共享,这样每个人都知道应用程序的状态。这就是持续集成服务器的用武之地。

在下一篇文章中,我们将配置我们的应用程序,由流行的开源 CI 服务器 Jenkins 构建。

Java CI/CD:从本地构建到 Jenkins 持续集成——Octopus 部署

原文:https://octopus.com/blog/java-ci-cd-co/from-local-to-ci

Java CI/CD: From local build to Jenkins Continuous Integration

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

在之前的文章中,我们采用了一个典型的 Java 应用程序,并创建了一个Dockerfile,负责构建代码并运行生成的 JAR 文件。通过利用 Maven 和 Java 本身等工具提供的现有 Docker 映像,我们创建了一个可重复和自包含的构建过程,并且生成的 Docker 映像可以由只安装了 Docker 的任何人执行。

这是我们构建过程的坚实基础。然而,随着越来越多的开发人员开始在共享代码库上工作,测试需求增加,产生的包变大,团队需要一个中央共享服务器来管理构建。这就是持续集成(CI)服务器的作用。

有许多 CI 服务器可用。其中最流行的是 Jenkins ,免费开源。在这篇博文中,我们将学习如何配置 Jenkins 来构建和发布我们的 Docker 映像。

Jenkins 入门

开始使用 Jenkins 最简单的方法是使用他们的 Docker 图像。正如我们在上一篇博文中为自己的应用程序创建了一个自包含映像一样,Jenkins Docker 映像为我们提供了在预配置的自包含环境中启动 Jenkins 的能力,只需几个命令。

首先,我们使用以下命令下载 Jenkins Docker 映像的最新长期支持(LTS)版本:

docker pull jenkins/jenkins:lts 

然后,我们用命令启动 Jenkins:

docker run -p 8081:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts 

-p参数将本地工作站的一个端口绑定到映像公开的一个端口。这里我们使用参数-p 8081:8080将本地端口8081绑定到容器端口8080。注意,因为我们自己的 PetClinic 应用程序在默认情况下也监听端口8080,所以我们为 Jenkins 选择了下一个可用的端口8081。将哪个本地端口映射到容器端口完全取决于您。参数-p 50000:50000公开了 Jenkins 代理使用的一个端口,我们将在后面的文章中配置这个端口来执行我们的构建。

-v参数将 Docker 卷挂载到容器中的路径。虽然 Docker 容器可以在运行时修改数据,但最好假设您将无法保留这些更改。例如,每次您调用docker run(您可以使用 Jenkins Docker 映像的更新版本)时,都会创建一个新的容器,其中没有任何被之前的容器修改过的数据。Docker 卷允许我们通过公开可以在容器之间共享的持久文件系统来保留修改过的数据。在本例中,我们创建了一个名为jenkins_home的卷,并将其挂载到目录/var/jenkins_home中。这意味着所有的 Jenkins 数据都是在一个永久卷中捕获的。

当 Docker 映像运行时,您将看到日志输出。作为初始引导的一部分,Jenkins 生成一个随机密码,并在日志中显示如下:

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

4b9e47bcd9ea469687dc39f23b0adb08

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************
*************************************************************
************************************************************* 

当您打开 http://localhost:8081 时,系统会提示您输入此密码来解锁 Jenkins:

Unlock Jenkins with the generated password 用生成的密码解锁詹金斯。

Jenkins 会提示你要么安装一个常用插件列表,要么只安装你选择的插件。安装推荐插件选项包含了我们需要的大部分插件。

插件下载需要一分钟左右的时间:

The plugins installing 插件安装。

完成后,创建第一个管理员用户,点击保存并继续

最后,定义 Jenkins URL 并点击保存并完成

Jenkins 现在已经配置好并可以使用了,只需点击开始使用 Jenkins

创建代理

我们需要解决的一个问题是,我们在 Docker 容器中运行 Jenkins,并且还希望 Jenkins 自己使用 Docker 来构建 Docker 映像。这就产生了一个我们想在 Docker 中使用 Docker 的场景。

在 Docker 中运行 Docker 是可能的,但是我们已经运行的 Jenkins 映像不支持开箱即用。已经创建了许多第三方工具,如 KanikoBuildah ,它们支持在不依赖 Docker 守护进程的情况下构建 Docker 映像。尽管这些解决方案相当先进。

一个更简单的解决方案是在 Jenkins Docker 容器之外运行 Jenkins 代理。该代理将在主机操作系统上运行,并有权访问 Docker 守护程序来创建 Docker 映像。

要创建代理,请单击 管理詹金斯➜管理节点和云 :

Jenkins Management options 詹金斯管理选项。

点击新节点链接,给新节点起一个名字,比如 Builder ,点击 OK :

输入远程根目录的路径。因为我在 Windows 上运行节点,所以路径类似于C:\JenkinsAgent。然后输入docker作为节点标签,点击保存:

配置新节点。

该节点现在在 Jenkins 中进行了配置,但是由于没有节点在运行,所以它显示为断开连接。

如果单击新节点,您将看到一个屏幕,其中提供了运行代理的说明。单击 agent.jar 链接下载代理文件,并运行屏幕上显示的命令将代理连接到 Jenkins:

Instructions for connecting a node 连接一个节点的指令。

现在,节点已连接,我们有一个连接到 Jenkins 的代理,它能够构建 Docker 映像。

安装 Docker 管道插件

詹金斯的初始配置安装了一些常用插件。然而,为了构建 Docker 镜像,我们还需要一个叫做 Docker Pipeline 的插件。这是通过 管理詹金斯➜管理插件 并在可用选项卡中搜索 Docker 管道插件来完成的。

下载和安装该插件需要几秒钟时间:

Downloading the plugin 下载插件。

添加 DockerHub 凭据

为了允许我们的项目将 Docker 映像发布到 Docker Hub,我们需要在 Jenkins 中定义 Docker Hub 凭证。

  1. 点击 管理詹金斯➜管理凭证 ,导航至凭证部分。
  2. 选择詹金斯,点击全球凭证
  3. 点击添加凭证,进入 Docker Hub 凭证,将 ID 设置为 dockerhub ,点击 OK 按钮:

Define the Docker Hub credentials 定义 Docker Hub 凭证。

我们现在拥有了在 Jenkins 中构建 Docker 映像所需的一切。下一步是定义 Jenkins 项目。

定义詹金斯项目

在高层次上,Jenkins 提供了两种类型的项目。

第一种格式称为自由式项目,在 Jenkins UI 中定义。虽然可以导出和共享自由式项目,但这很难做到,因为底层数据格式不是为手动编辑而设计的。

第二种格式称为管道,本质上是一个脚本,其创建和管理方式与应用程序中的代码非常相似。管道可以与您的项目代码一起保存在一个名为Jenkinsfile的文件中,它将您的应用程序代码和构建定义保存在同一个地方。

我们将为我们的项目创建一个Jenkinsfile,用下面的代码构建并发布我们的 Docker 图像:

pipeline {
    agent {
        label 'docker'
    }
    stages {
        stage('Building our image') {
            steps {
                script {
                    dockerImage = docker.build "mcasperson/petclinic:$BUILD_NUMBER"
                }
            }
        }
        stage('Deploy our image') {
            steps {
                script {
                    // Assume the Docker Hub registry by passing an empty string as the first parameter
                    docker.withRegistry('' , 'dockerhub') {
                        dockerImage.push()
                    }
                }
            }
        }
    }
} 

让我们把这个文件分解一下。

所有声明性管道都以pipeline开头:

pipeline { 

运行这个构建的代理在agent部分中定义。在这里,我们已经将构建配置为在任何带有标签docker的代理上运行。这确保了构建运行在我们的外部节点上,它可以访问 Docker 守护进程,而不是运行在 Jenkins 服务器本身上,它不能访问 Docker:

 agent {
        label 'docker'
    } 

组成流水线的阶段包含在stages部分:

stages { 

第一阶段构建 docker 映像。我们利用脚本步骤调用我们之前安装的 Docker Pipeline 插件来构建映像。这个构建的结果保存在一个名为dockerImage的变量中。注意使用$BUILD_NUMBER变量为每个构建分配一个新版本作为图像标签。这确保了该管道的每次执行都将构建一个新的不同的 Docker 映像:

 stage('Building our image') {
            steps {
                script {
                    dockerImage = docker.build "mcasperson/petclinic:$BUILD_NUMBER"
                }
            }
        } 

第二阶段将新创建的映像推送到 Docker Hub。withRegistry方法将 Docker 注册表作为第一个参数,或者如果它留空,则默认为 Docker Hub。第二个参数是我们之前在 Jenkins 中创建的凭证的名称:

 stage('Deploy our image') {
            steps {
                script {
                    // Assume the Docker Hub registry by passing an empty string as the first parameter
                    docker.withRegistry('' , 'dockerhub') {
                        dockerImage.push()
                    }
                }
            }
        } 

这个文件和我们的应用程序代码一起被提交。下一步是创建一个 Jenkins 项目来检查代码并运行管道。

创建管道项目

在 Jenkins 仪表板上,单击新项目链接。输入 Petclinic 作为项目名称,并选择管道选项:

New project creation 新项目创建。

管道部分下,从 SCM 中选择管道脚本,输入 Git 存储库 URL(本例中为https://github.com/mcasperson/spring-petclinic.git)并选择要构建的分支(本例中为 main )。然后点击保存:

Define the pipeline GIT repository 定义管道 GIT 库。

从项目仪表板中,单击 Build Now 链接手动运行一个构建,并单击构建链接图标:

A Jenkins project build 一个詹金斯项目的构建。

点击控制台输出链接查看构建输出:

Jenkins project build console output 詹金斯项目构建控制台输出。

当构建完成时,Docker 映像在 Jenkins 节点上构建并被推送到 Docker Hub ,带有基于构建号的标签:

The resulting image in Docker Hub 由此产生的图像出现在 Docker 中枢。

这样,我们就成功地配置了 Jenkins 来编译和测试应用程序代码,然后构建 Docker 映像并将其推送到 Docker Hub。

结论

Jenkins 提供了一个中央平台,多个开发人员可以通过这个平台构建、测试和分发他们的代码。Jenkins 维护更改的历史记录以及这些更改是否导致了成功的构建,维护分发 Docker 映像所需的凭证,并消除个人在每次构建时上传可能很大的 Docker 映像的需要。

在本文中,我们逐步完成了将 Jenkins 作为 Docker 容器运行的过程,连接了一个节点来执行构建,并编写了一个 Jenkins 管道来定义 Jenkins 将如何构建和推送 Docker 映像。这样做的最终结果是一个持续的集成系统,它可以自动构建和发布我们的应用程序,消除了单个开发人员手动管理这个过程的需要。我们实现了持续整合。

下一步是在某个地方部署我们新创建的 Docker 映像。为此,我们将在 AWS 中配置一个 Kubernetes 集群。

浏览 DevOps 工程师手册以了解更多关于持续集成和持续交付(CI/CD)的信息。

有趣的输出变量-章鱼部署

原文:https://octopus.com/blog/fun-with-output-variables

在 Octopus 2.4 中,我们增加了一个步骤中的变量在另一个步骤中可用的能力。例如,您可能有一个名为 StepA 的独立 PowerShell 步骤,它的功能如下:

Set-OctopusVariable -name "TestResult" -value "Passed" 

然后,您可以在后续部署步骤(在同一个部署中)中使用它,如下所示:

$TestResult = $OctopusParameters["Octopus.Action[StepA].Output.TestResult"] 

内置输出变量

在一个步骤运行之后,Octopus 捕获输出变量,并保存它们以供后续步骤使用。除了您自己使用Set-OctopusVariable创建的变量,Octopus 还提供了许多内置变量:

  • 对于 NuGet 包步骤:
    • Octopus.Action[StepName].Output.Package.InstallationDirectoryPath:包部署到的路径。
  • 对于手动干预步骤:
    • Octopus.Action[StepName].Output.Manual.Notes:响应手动步骤输入的注释。
    • Octopus.Action[StepName].Output.Manual.ResponsibleUser.Id
    • Octopus.Action[StepName].Output.Manual.ResponsibleUser.Username
    • Octopus.Action[StepName].Output.Manual.ResponsibleUser.DisplayName
    • Octopus.Action[StepName].Output.Manual.ResponsibleUser.EmailAddress

输出变量的范围和索引

重要的是要记住,与构建服务器不同,Octopus 在许多机器上并行运行各个步骤。这意味着每台机器可能对相同的输出变量产生不同的值。

例如,假设我们有两台机器, App01App02 ,我们在这两台机器上运行这个脚本:

Set-OctopusVariable -name "MyMachineName" -value [System.Environment]::MachineName 

显然,我们将有两个不同的值可用,因为两台机器有不同的主机名。为了处理这一点,Octopus 创建了变量,并将它们限定在一台机器上。在这个例子中,Octopus 将存储两个变量:

Octopus.Action[StepA].Output.MyMachineName = App01     (Scope: App01)  # Value from App01 machine
Octopus.Action[StepA].Output.MyMachineName = App02     (Scope: App02)  # Value from App02 machine 

从现在开始,在这些机器上运行的部署中的任何步骤都将从该机器获得适用的输出变量。这意味着您可以:

$name = $OctopusParameters["Octopus.Action[StepA].Output.MyMachineName"] 

有时,您可能需要从一台机器上访问由另一台机器产生的变量。在这种情况下,Octopus 还存储非作用域变量,这些变量由机器用索引:

Octopus.Action[StepA].Output[App01].MyMachineName = App01              # Value from App01 machine
Octopus.Action[StepA].Output[App02].MyMachineName = App02              # Value from App02 machine 

这意味着,例如在 App03 上运行的后续步骤中,您可以:

$app01Name = $OctopusParameters["Octopus.Action[StepA].Output[App01].MyMachineName"]
$app02Name = $OctopusParameters["Octopus.Action[StepA].Output[App02].MyMachineName"]
# Do something with $app01Name and $app02Name 

记住$OctopusParameters只是一个Dictionary<string,string>。这意味着你可以这样做:

$MatchRegex = "Octopus\.Action\[StepA\]\.Output\[(.*?)\]\.MyMachineName"

Write-Host "Machine names:"
$OctopusParameters.GetEnumerator() | Where-Object { $_.Key -match $MatchRegex } | % { 
  Write-Host "$_.Value"
} 

这里,我们迭代字典中的所有键/值对,并找到与我们的 regex 匹配的键/值对,regex 在变量键的机器名组件上有一个通配符。

查找先前软件包的安装位置

对于输出变量来说,这是一个如此常见的用例,以至于我想显式地调用它。

默认情况下,为了避免各种问题,破坏部署和文件锁,触须自动提取包到一个新的,干净的目录。如果您多次部署完全相同的包,您将会得到类似如下的结果:

C:\Octopus\Applications\Production\MyApp\1.0.0
C:\Octopus\Applications\Production\MyApp\1.0.0_1
C:\Octopus\Applications\Production\MyApp\1.0.0_2
C:\Octopus\Applications\Production\MyApp\1.0.0_3 

假设您部署了一个 NuGet 包,但是想要编写一个独立的 PowerShell 脚本,该脚本在包被提取到的目录中的同一服务器上运行,但是不是 NuGet 包的一部分。你可以用这个:

$packageDir = $OctopusParameters["Octopus.Action[MyApp].Output.Package.InstallationDirectoryPath"]
cd $packageDir

# Do your custom logic 

摘要

应用程序部署通常涉及在许多不同的机器上运行部署包和执行代码。Octopus 中的输出变量提供了一种非常强大的方式来在不同的步骤和不同的机器之间共享这些值。我希望你会发现这个功能很有用!

了解更多信息

集装箱化-你需要什么开始-八达通部署

原文:https://octopus.com/blog/get-started-containers

容器正在成为运行和托管应用程序和微服务的最流行的方式。简而言之,容器是轻量级的虚拟环境,可以运行应用程序,而不需要庞大的完整操作系统。

没有膨胀,容器比传统的基础设施和虚拟机有许多好处,包括:

  • 更好的安全性
  • 很少甚至没有系统维护
  • 易于旋转和拆卸
  • 轻松扩展资源以满足应用需求

可部署的容器映像也使您的应用程序运行起来更加容易。容器映像通常包括您的软件、运行应用程序所需的所有运行时和先决条件,以及由代码设置的任何配置。

多年来,许多公司都有自己的容器图像格式,但是 Docker 的“OCI”图像(现在是开源的)很快成为了行业标准。事实上,许多供应商现在互换使用术语“OCI 图像”和“码头图像”。

OCI 主张开放集装箱倡议。该计划是一个容器结构,作为行业标准格式。技术、开发和云服务领域的大多数主要参与者都支持这项倡议,并支持 OCI 格式。在开放容器倡议网站上了解更多信息。

对于那些不熟悉集装箱化概念的人,让我们从高层次上看一下您需要什么,以及它们是如何组合在一起的。

Docker 桌面

适用于 Windows、Mac 和 Linux 的 Docker Desktop 是开始容器化的最简单方法。

它帮助您从所选的操作系统中执行以下操作:

  • 创建、运行和测试容器就绪应用
  • 构建现成的容器化工具和环境,比如 NGINX、MySQL 或 Ubuntu
  • 管理您的图像并将其发送到存储库和注册表
  • 创建开发环境

在撰写本文时,其中一些功能还处于测试或预览阶段。

在 Windows 上,您可以在 Linux 和 Windows 容器映像之间切换兼容性。在大多数情况下,我们建议使用 Linux 映像,因为很少有托管服务支持本机 Windows 映像。

虽然 Docker Desktop 提供了友好的图形界面,但超过 250 名员工的公司每月都要付费。熟悉命令行的 Linux 用户可以像往常一样继续使用容器。

主办;主持

如果你想在一个容器中运行你的应用程序,你需要一个地方来托管它以便人们访问。许多供应商提供容器托管,而不仅仅是三大巨头微软 Azure、T2、谷歌云和 T4 亚马逊网络服务。

您不局限于云服务,因为大多数主要操作系统都支持 Docker 映像。所以,如果你想在你自己的硬件上运行你的应用,比如服务器或者你自己的测试电脑,你可以。

其他示例托管提供商包括:

集装箱登记处

容器注册中心既是存储库的集合(后面将详细介绍),也是用于管理和部署映像的可搜索目录。

注册表在两个重要方面有所帮助:

  1. 他们确保每个人在搜索时都能获得正确版本的软件,无论他们身在何处。
  2. 部署过程使用注册表从存储库中调用正确的映像。

市场上有大量的注册中心,它们都有不同的优势,例如:

  • 私有注册表
  • 地理位置
  • 流行的公共注册表
  • 内部选项
  • 与管道其他区域的兼容性

当你选择一个主机提供商时,你可能会被锁定在一个特定的注册表中。

示例容器注册表包括:

集装箱仓库

注册中心对您的图像进行分类,容器存储库存储这些图像以备部署。通常,您的注册中心供应商会在平面图上提供您的存储库存储空间。

部署您的应用程序时,您的流程将:

  • 检查注册表中的正确版本
  • 从存储库中提取图像
  • 将映像部署到托管服务上新启动的容器中

要部署的东西

如果您想尝试容器化,您需要部署一些东西。如果你没有合适的,我们会帮你的。

章鱼水下应用是一个简单的 JavaScript 应用,帮助你测试不同服务提供商的集装箱化。

The Octopus Underwater App

看看它是如何工作的

Terence 在我们最近的 CI 系列中写了一篇优秀的容器部署指南,展示了这些概念是如何结合在一起的。

他的指南将带您了解完整的流程,包括:

  • 克隆章鱼水下应用的 GitHub 库
  • 建立码头工人形象
  • 将图像添加到谷歌云的注册和存储服务中
  • 从 Google 云注册表部署到 Azure Kubernetes 集群

下一步是什么?

在这篇文章中,我们探索了使容器化应用成为可能的基本组件。我们还有更多关于集装箱化的帖子,包括:

  • 您应该考虑的注册中心
  • 更详细地看集装箱化的好处
  • 微服务的良好平台
  • 深入探讨云流程编排和云自动化
  • 看看“一切都是代码”

愉快的部署!

从 SSISDB 导入变量- Octopus Deploy

原文:https://octopus.com/blog/get-variables-from-ssisdb

介绍

如果您曾经使用过从包中部署 ispac SSIS 项目或从引用的包中部署 ispac SSIS 项目步骤模板,那么您会知道它可以从您的 SSIS 包中提取项目参数和连接管理器信息,并在 SSISDB 中将它们创建为环境变量。您还知道,在执行此操作时,连接管理器的每个属性都被创建为一个单独的环境变量。如果您的 Octopus 项目中没有同名的变量,您会得到一条消息:

OctopusParameters 集合为空或 CM。OctoDemoSql . adventureworks 2017 . sa . connectusingmanagedidentity 不在集合中

该变量仍在 SSISDB 环境中创建,但是,它默认为设计时值。如果您的 SSIS 包有大量的项目参数和/或连接管理器,那么变量列表会非常庞大,老实说,一个一个地创建是非常乏味的。

自动化拯救世界!

作为从一个包部署 ispac SSIS 项目的最初作者,我可以告诉你,我的 SSIS 开发人员拿着干草叉和火把敲我办公室的门,咆哮着说创建所有这些变量是多么耗时。为了避免死于他们眼中的匕首,我求助于 PowerShell 和 Octopus Deploy API,想出一种方法来从 SSISDB 环境中检索变量,并将它们导入到他们的 Octopus Deploy 项目中。

剧本

以下脚本用于演示目的。

下面的脚本从 SSISDB 环境中提取变量和值,并在 Octopus Deploy 中将它们创建为项目变量!这节省了大量的时间,开发人员满意地离开了我的办公室,他们的要求得到了满足。

# Define parameters
param(
    $OctopusServerUrl,
    $APIKey
)

# Define functions
Function Get-EnvironmentVariablesFromSSISDB
{
    # Define parameters
    param(
        $UseIntegratedAuthentication,
        $SqlUserName,
        $SqlPassword,
        $CatalogName,
        $FolderName,
        $EnvironmentName,
        $SqlServerName
    )

    # Import needed assemblies
    [Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Management.IntegrationServices") | Out-Null # Out-Null supresses a message that would normally be displayed saying it loaded out of GAC

    # Create a connection to the server
    $sqlConnectionString = "Data Source=$SqlServerName;Initial Catalog=master;"

    # Check authentication
    if ($UseIntegratedAuthentication)
    {
        # Add integrated
        $sqlConnectionString += "Integrated Security=SSPI;"
    }
    else
    {
        # ass username password
        $sqlConnectionString += "User ID=$SqlUserName; Password=$SqlPassword"    
    }

    $sqlConnection = New-Object System.Data.SqlClient.SqlConnection $sqlConnectionString

    # create integration services object
    $integrationServices = New-Object "$ISNamespace.IntegrationServices" $sqlConnection

    try
    {
        # get catalog reference
        $Catalog = Get-Catalog -CatalogName $CataLogName -IntegrationServices $integrationServices
        $Folder = Get-Folder -FolderName $FolderName -Catalog $Catalog
        $Environment = Get-Environment -Folder $Folder -EnvironmentName $EnvironmentName

        # return environment variables
        return $Environment.Variables
    }
    finally
    {
        # close connection
        $sqlConnection.Close()
    }
}

Function Get-Folder
{
    # parameters
    Param($FolderName, $Catalog)

    # try to get reference to folder
    $Folder = $Catalog.Folders[$FolderName]

    # check to see if $Folder has a value
    if(!$Folder)
    {
        Write-Error "Folder not found."
        throw
    }

    # return the folde reference
    return $Folder
}

Function Get-Environment
{
    # define parameters
    Param($Folder, $EnvironmentName)

    # get reference to Environment
    $Environment = $Folder.Environments[$EnvironmentName]

    # check to see if it's a null reference
    if(!$Environment)
    {
        Write-Error "Environment not found."
        throw
    }

    # return the environment
    return $Environment
}

Function Get-Catalog
{
    # define parameters
    Param ($CatalogName, $IntegrationServices)

    # define working varaibles
    $Catalog = $null

    # check to see if there are any catalogs
    if($integrationServices.Catalogs.Count -gt 0 -and $integrationServices.Catalogs[$CatalogName])
    {
        # get reference to catalog
        $Catalog = $integrationServices.Catalogs[$CatalogName]
    }
    else
    {
        Write-Error  "Catalog $CataLogName does not exist or the Tentacle account does not have access to it."

        # throw error
        throw
    }

    # return the catalog
    return $Catalog
}

Function Get-OctopusProject
{
    # Define parameters
    param(
        $OctopusServerUrl,
        $ApiKey,
        $ProjectName
    )

    # Call API to get all projects, then filter on name
    $octopusProject = Invoke-RestMethod -Method "get" -Uri "$OctopusServerUrl/api/projects/all" -Headers @{"X-Octopus-ApiKey"="$ApiKey"}

    # return the specific project
    return ($octopusProject | Where-Object {$_.Name -eq $ProjectName})
}

Function Get-OctopusProjectVariables
{
    # Define parameters
    param(
        $OctopusDeployProject,
        $OctopusServerUrl,
        $ApiKey
    )

    # Get reference to the variable list
    return (Invoke-RestMethod -Method "get" -Uri "$OctopusServerUrl/api/variables/$($OctopusDeployProject.VariableSetId)" -Headers @{"X-Octopus-ApiKey"="$ApiKey"})
}

Function Update-ProjectVariables
{
    param(
        $OctopusServerUrl,
        $ProjectVariables,
        $ApiKey
    )

    # Convert the object into JSON
    $jsonBody = $ProjectVariables | ConvertTo-Json -Depth 5

    # Call the API to update
    Invoke-RestMethod -Method "put" -Uri "$OctopusServerUrl/api/variables/$($ProjectVariables.Id)" -Body $jsonBody -Headers @{"X-Octopus-ApiKey"="$ApiKey"}
}

try
{
    # Store the IntegrationServices Assembly namespace to avoid typing it every time
    $ISNamespace = "Microsoft.SqlServer.Management.IntegrationServices"
    $CataLogName = "SSISDB"

    # Get reference to project
    $octopusProject = Get-OctopusProject -OctopusServerUrl "<Your URL Here>" -ApiKey "<Your API key here>" -ProjectName "<Octopus Deploy project name>"

    # Get list of existing variables
    $octopusProjectVariables = Get-OctopusProjectVariables -OctopusDeployProject $octopusProject -OctopusServerUrl "<Your URL Here>" -ApiKey "<Your API key here>"

    # Get list of SSIS project variables
    $ssisEnvironmentVariables = Get-EnvironmentVariablesFromSSISDB -UseIntegratedAuthentication $false -SqlServerName "<Sql server name>" -SqlUserName "<sql account user name>" -SqlPassword "<sql account password>" -CatalogName "SSISDB" -FolderName "<SSISDB folder name>" -EnvironmentName "<SSISDB environment name>"

    # Loop through the ssis variable set
    foreach ($variable in $ssisEnvironmentVariables)
    {
        # Check to see if variable already exists in Octopus project variables
        if ($null -eq ($octopusProjectVariables.Variables | Where-Object {$_.Name -eq $variable.Name}))
        {
            # Display message
            Write-Output "Adding $($variable.Name) to Octopus Deploy project $($octopusProject.Name)"

            # Create new variable hash table
            $newVariable = @{
                #Id = "$(New-Guid)"
                Name = "$($variable.Name)"
                Value = "$($variable.Value)"
                Description = $null
                Scope = @{}
                IsEditable = $(if ($variable.Sensitive) { $false} else {$true})
                Prompt = $null
                Type = "String"
                IsSensitive = $(if ($variable.Sensitive) { $true} else {$false})
            }

            # Add variable
            $octopusProjectVariables.Variables += $newVariable
        }
    }

    # Update the project
    Update-ProjectVariables -ProjectVariables $octopusProjectVariables -ApiKey $APIKey -OctopusServerUrl $OctopusServerUrl
}
catch
{
    Write-Error $_.Exception.Message

    throw
} 

摘要

在本文中,我向您展示了一种快速填充 Octopus Deploy 项目变量的方法,方法是连接到 SSISDB 并复制环境变量。该解决方案要求至少部署一次 SSIS 包,以便在 SSISDB 中填充环境变量。虽然这个例子是特定于 SSISDB 的,但是使用 API 以编程方式向 Octopus Deploy 项目添加变量的一般方法可以用于各种各样的源。

Bamboo - Octopus 部署入门

原文:https://octopus.com/blog/getting-started-with-bamboo

持续集成(CI)服务器是 CI/CD 流程的重要组成部分。CI 服务器获取一个代码存储库,构建它,并将其推到一个中心位置,在那里像 Octopus 这样的连续交付(CD)工具可以接管和管理部署。

Bamboo 是由 Atlassian 开发的 CI 服务器,可以自动构建和测试软件应用程序。如果您正在开始您的 CI/CD 之旅,从一个简单的用例开始,并有一个可见的结果,会很有帮助。

在本文中,我将向您展示如何构建和推送一个软件构件,Octopus 可以接管它并将其部署到目标环境中。

您将学习如何:

  • 在 Windows 服务器上安装 Bamboo
  • 配置 Bamboo 项目
  • 配置一个 Bamboo 计划来构建 Docker 容器并将其推送到容器注册中心
  • 运行并查看容器图像

正在设置

要关注这篇文章,您需要以下软件和帐户:

在 Windows 服务器上安装 Bamboo

要安装 Bamboo:

  • 注册一个免费试用版,它会给你一个激活密钥
  • 运行安装可执行文件
  • 将安装位置设置为您可以访问的目录,例如C:\Users\Username\Documents(注意,将其设置为 C:\Program files 的默认位置可能会导致权限错误)
  • 设置 Bamboo 主目录,并确保这是一个独立于安装位置的目录,文件夹名为Bamboo-home

安装完成后,运行 Bamboo 服务器:

  • 打开终端并导航到 Bamboo 安装目录
  • 运行bin\start-bamboo.bat
  • 服务器应该在http://localhost:8085/启动

设置用户

在启动屏幕中,会要求您设置一个管理员帐户。填写详细信息并将详细信息存储在密码管理器中。如果你把密码放错了地方,你需要运行一个恢复过程。

代理人

代理是在 Bamboo 中执行工作负载的工人。因为您安装了先决条件技术,所以您可以使用本地机器作为代理进行测试。

要设置本地代理:

  • 在 Bamboo 仪表板中,转到设置图标并选择代理
  • 转到添加本地代理并为其命名
  • 点击添加

设置项目和计划

Bamboo 将您的工作流程组织成项目和计划。一个项目可以包含多个计划,每个计划是一个执行一系列任务的过程。

首先,设置您的第一个项目和计划:

  • 在主菜单中,选择创建,然后选择创建计划
  • 填写您的项目和计划的名称

Create Project and Plan in Bamboo

在下一个屏幕上,选中显示 Link new repository 的框。

连接到章鱼水下应用程序库

本帖使用章鱼水下 app

要使用此存储库:

  • 把它存入你自己的 GitHub 账户
  • 在 GitHub 的密码设置中,使用一个个人访问令牌来授权 Bamboo 访问你的 GitHub 账户下的存储库
  • 选择主分支
  • 测试连接以确保 Bamboo 连接到这个存储库
  • 点击保存并继续

配置作业

配置作业屏幕上,配置计划为执行您的作业而运行的任务。Bamboo 提供了一套任务步骤供您选择。这些任务执行 CI 路径中的某个步骤,如检出、构建、拉取、推送。

有一个为您预先填写的源代码签出任务。这将链接的 GitHub 存储库签出到 Bamboo 中。

  • 将隔离的构建作为代理环境。这将使用您之前设置的本地代理。

首先,添加构建 Docker 任务:

  • 点击添加任务并搜索Docker
  • 将命令设置为Build a Docker Image
  • 将存储库设置为[Your DockerHub Username]/[The tag of your image]
  • 检查使用位于上下文路径中的现有 docker 文件
  • 点击保存

现在,添加 Push Docker 任务:

  • 点击添加任务并搜索Docker
  • 将命令设置为Push a Docker Image
  • 将存储库设置为[Your DockerHub Username]/[The tag of your image]
  • 检查使用代理的本地凭证
  • 点击保存
  • 点击创建

该计划通过检出代码、构建 Docker 映像并将构建的映像推送到 DockerHub 开始执行

完成后,您会看到一个绿色的勾号框,表示计划成功完成。

Bamboo Success

导航到您的 DockerHub 帐户,确认图像已被推送到存储库。

部署步骤

现在映像在 DockerHub 上,任何 CD 工具都可以将它部署到本地或云平台。我们有指南解释如何为以下人员完成此操作:

要在本地查看应用程序:

  • docker pull [Your DockerHub Username]/[The tag of your image]
  • docker run -p 8080:8080 [Your DockerHub Username]/[The tag of your image]
  • http://localhost:8080/

你看到章鱼水下应用程序,你可以了解更多关于 CI/CD 和章鱼的信息。

Octopus Underwater App

结论

CI 服务器是 CI/CD 流程的重要组成部分,您可以使用许多不同的 CI 服务器和 Octopus Deploy 来完成您的部署。Atlassian 的 Bamboo 允许您构建 Docker 映像并将其推送到 Docker 存储库。

在这篇文章中,你学习了如何安装 Bamboo 和建立一个项目,并计划构建和推送 Octopus 水下应用程序。这是一个简单的入门示例,但使用竹子的方式还有很多。

如果您对更多 CI 服务器资源感兴趣,请查看我们关于 CI 服务器的系列,其中我们重点介绍了 Jenkins、GitHub 操作和基本 CI 概念。

愉快的部署!

Kind 和 Octopus - Octopus 部署入门

原文:https://octopus.com/blog/getting-started-with-kind-and-octopus

Getting started with Kind and Octopus

当您第一次开始使用 Kubernetes 时,在部署哪怕是最简单的示例应用程序之前,可用的工具和选项的数量会是一个很大的障碍。与大多数其他平台不同,Kubernetes 不提供可以下载并安装到本地开发 PC 上的标准包。社区用许多选项填补了这一空白,如 MinikubeMicroK8sk3sDocker Desktop with Kubernetes

在这篇博文中,我们将关注。虽然前面提到的任何解决方案都是很好的选择,但我更喜欢 Kind,因为它可以无缝地跨所有主要操作系统工作,并且在 WSL2 中运行良好,这使得 Windows 开发人员可以很容易地在 Windows 和 Linux 之间切换。

安装种类

Kind 创建一个 Kubernetes 集群作为 Docker 容器。想到实现 Kubernetes 平台的 Docker 容器可能有点令人费解,这反过来又编排了更多的 Docker 容器,但在实践中,建立一个友好的 Kubernetes 集群的过程既快又容易。

安装好 Docker 之后,安装 kubectl类可执行文件。kubectl 和 Kind 都是自包含的可执行文件,这意味着它们只需要下载并保存在您的路径下的一个目录中。

然后用命令kind create cluster创建一个集群。这个命令使用名为kind-kind的集群和用户在~/.kube/config创建或更新 Kubernetes 配置文件。下面显示了一个config文件的例子:

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSU...
    server: https://127.0.0.1:55827
  name: kind-kind
contexts:
- context:
    cluster: kind-kind
    user: kind-kind
  name: kind-kind
current-context: kind-kind
kind: Config
preferences: {}
users:
- name: kind-kind
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSU...
    client-key-data: LS0tLS1CRUdJTiBSU0EgUF... 

要验证集群是否正在运行,请执行kubectl get nodes。您应该会看到类似这样的输出:

$ kubectl get nodes
NAME                 STATUS   ROLES    AGE    VERSION
kind-control-plane   Ready    master   101s   v1.18.2 

我们现在有了一个本地 Kubernetes 集群,可以进行测试了。

Kind 创建的config文件嵌入了用于保护 API 流量的集群证书,以及用于识别 Kubernetes 用户的客户端密钥和证书。我们需要将这些值提取到可以导入 Octopus 的文件中。

下面的 Bash 和 PowerShell 脚本提取数据,对其进行解码,并将客户端密钥和证书合并到一个 PFX 文件中。这些脚本给了我们两个文件:cluster.crtclient.pfx:

下面是 Bash 脚本:

kubectl config view --raw -o json | jq -r ".users[] | select(.name==\"$1\") | .user[\"client-certificate-data\"]" | base64 -d > client.crt
kubectl config view --raw -o json | jq -r ".users[] | select(.name==\"$1\") | .user[\"client-key-data\"]" | base64 -d > client.key
kubectl config view --raw -o json | jq -r ".clusters[] | select(.name==\"$1\") | .cluster[\"certificate-authority-data\"]" | base64 -d > cluster.crt
openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx -passout pass:
rm client.crt
rm client.key 

下面是 PowerShell 脚本,其中的openssl可执行文件是从这里的下载的:

param($username)

kubectl config view --raw -o json |
  ConvertFrom-JSON |
  Select-Object -ExpandProperty users |
  ? {$_.name -eq $username} |
  % {
    [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($_.user.'client-certificate-data')) | Out-File -Encoding "ASCII" client.crt
    [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($_.user.'client-key-data')) | Out-File -Encoding "ASCII" client.key
    & "C:\Program Files\OpenSSL-Win64\bin\openssl" pkcs12 -export -in client.crt -inkey client.key -out client.pfx -passout pass:
    rm client.crt
    rm client.key
  }

  kubectl config view --raw -o json |
  ConvertFrom-JSON |
  Select-Object -ExpandProperty clusters |
  ? {$_.name -eq $username} |
  % {
    [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($_.cluster.'certificate-authority-data')) | Out-File -Encoding "ASCII" cluster.crt
  } 

创建章鱼 Kubernetes 目标

文件cluster.crtclient.pfx被上传到八达通证书商店。这里我将这些证书称为类用户类集群证书:

我们还需要一个本地环境:

最后一步是创建 Kubernetes 目标。该目标使用证书种类用户进行身份验证,使用种类集群证书进行服务器证书授权,使用 https://127.0.0.1:55827 进行集群 URL。这个 URL 来自 Kubernetes config文件中的clusters[].clusters.server字段:

关于工人的一句话

因为 Kubernetes URL 引用了127.0.0.1(或localhost),所以我们要么需要在本地开发 PC 上运行 Octopus,要么需要在本地 PC 上安装一个 Worker,这允许远程 Octopus 实例通过隧道进入我们的本地 PC。

在下面的截图中,你可以看到触手管理器配置一个工人的一些步骤:

选择一个轮询触手:

将触手配置为工作者:

向 Octopus 服务器注册员工:

这里我们可以看到分配给默认工作线程池的新工作线程:

工作人员就位后,远程 Octopus 服务器上的 Kubernetes 目标现在可以访问我们的本地 Kubernetes 集群:

结论

我们现在已经成功地用 Kind 创建了一个本地 Kubernetes 集群,从 Kubernetes 配置文件中提取了证书,将证书导入到 Octopus 中,并在 Octopus 中创建了一个 Kubernetes 目标,它通过一个 Worker 连接到我们的本地集群。

从这里,我们可以了解如何使用 Octopus 来部署 Kubernetes 资源。以下博客文章向您展示了如何:

LDAP 身份验证提供者入门- Octopus 部署

原文:https://octopus.com/blog/getting-started-with-ldap-auth-provider

在 Octopus Deploy 2021.2 中,我们增加了轻量级目录访问协议(LDAP) 认证提供者。

在我们的发布公告中了解更多关于 Octopus 2021.2 (Q3)发布的信息。

许多客户希望迁移到 Octopus Linux 容器,但是他们必须通过 Active Directory 进行身份验证。Active Directory 也是一个 LDAP 服务器,这意味着通过新的 LDAP 提供程序,您现在可以使用 Octopus Linux 容器对 Active Directory 进行身份验证。

在本文中,我将带您了解配置 LDAP 身份验证提供者的步骤。在这篇文章结束时,我的 Octopus Deploy 实例将通过 LDAP 认证到我的本地域,devopswalker.local,运行在 Windows Server 2019 上。

本文假设您熟悉目录服务的核心概念。如果您对这些概念不确定,请与您当地的系统管理员联系。

LDAP 背景

LDAP 或轻量级目录访问协议是一种开放的、厂商中立的、行业标准的协议,用于与目录服务器进行交互。

很容易将 LDAP 与目录服务器(如 Active Directory)混淆。LDAP 本身不是目录服务器。它是用于与目录服务器通信的协议,就像http是用于 web 服务器的协议,或者wss是通过套接字与 web 服务器通信的协议。

Active Directory 的默认配置启用 LDAP 支持。如果您在本地运行 Active Directory,您可能已经有一个 LDAP 服务器。

为什么是 LDAP?

有三个主要的使用案例解释了我们为什么添加 LDAP 支持:

  1. 并非所有人都在运行 Active Directory。LDAP 是厂商中立的,所以更多的非微软用户可以利用外部认证。大多数(如果不是全部)目录服务器支持 LDAP。
  2. Active Directory /集成身份验证要求将服务器添加到域中。这不适用于 Octopus Linux 容器。
  3. 使用非 Windows 客户端(特别是 macOS)的用户将拥有与 Windows 客户端相同的体验。有了 Active Directory,如果你在 macOS 上运行的 Chrome 上点击用域名账户按钮登录,你会发现自己陷入了一个无休止的登录提示循环。至少我做到了(而且我没有耐心去修复它)。

使用 LDAP 还有其他优势,比如跨域查询。我推荐阅读《ldap.com》,了解 LDAP 能提供什么和不能提供什么。这是一个灵活的协议,每个目录服务器,无论是 Active Directory 还是 OpenLDAP,都提供了很多功能。

首先保护您的 LDAP 服务器

默认情况下,LDAP 流量不加密。通过监控网络流量,窃听者可以知道你的 LDAP 密码。

在 Octopus Deploy 中配置 LDAP 提供程序之前,请查阅目录服务器的供应商文档,以便通过 SSL 或 TLS 进行通信。

保护 LDAP 服务器不在本文的讨论范围之内,每个供应商都有自己的独特之处。本文的其余部分假设您与系统管理员一起保护了 LDAP 服务器。

了解 DNs

在 LDAP 中,DN(可分辨名称)唯一地标识目录信息树中的条目和位置,就像文件系统中文件的路径。

如前所述,我的领域是devopswalker.local
把它翻译成 LDAP 可以理解的 DN 就是dc=devopswalker,dc=local

存储我的目录服务器的所有用户和组都有一个公共 DNcn=users,dc=devopswalker,dc=local

我的用户账号Bob Walker DN 是cn=Bob Walker,cn=users,dc=devopswalker,dc=local

你需要什么

在配置 LDAP 之前,您需要以下内容:

  • 要查询的服务器的完全限定域名或 FQDN。在我的例子中是DC01.devopswalker.local
  • 要使用的端口号和安全协议。我对我的域控制器和 SSL 使用标准的安全 LDAP 端口 636。
  • 可以执行用户和组查找的服务帐户的用户名和密码。在我的例子中是cn=Octopus Service,cn=users,dc=devopswalker,dc=local
  • 您希望用于用户和组查找的根 DN。在我的例子中,两者都是cn=users,dc=devopswalker,dc=local

使用诸如 Windows ldp.exe 或 LDAP Administrator 之类的工具找到你想要开始的树/森林的最高部分。例如,从根开始dc=devopswalker,dc=local可能会导致性能问题,因为 LDAP 查询被迫遍历数百或数千条记录。要处理的数据越少,查询就越快。由于我的 active directory 配置,所有用户和组都存储在cn=users,dc=devopswalker,dc=local中。您的服务器可能不同。

the results of an ldp browser

在 Octopus Deploy 中配置 LDAP 身份验证提供程序

导航至配置➜设置➜ LDAP 。在以下字段中输入值:

  • 服务器:输入服务器的 FQDN。
  • 端口:更改端口(如果您的安全端口不同于默认端口)。
  • 安全协议:更改为 SSL 或 StartTLS。
  • 用户名:输入将用于执行用户查找的用户名。可以是[username]@[domain name]也可以是用户的 DN。
  • 用户基本 DN:输入用户的基本 DN,在我的例子中是cn=users,dc=devopswalker,dc=local
  • 组基本 DN:输入您的组的基本 DN,在我的例子中是cn=users,dc=devopswalker,dc=local
  • 已启用:选中复选框以启用该功能。

如前所述,我使用的是运行在 Windows Server 2019 上的 Active Directory。您的根 DN 可能不同。请咨询系统管理员或使用 LDAP 浏览器为您的目录服务找到正确的用户和群组 DNs。

basic configuration for LDAP authentication provider

测试 LDAP 身份验证提供程序

在我配置了 LDAP 身份验证提供者之后,我犯了一个错误,注销并重新登录。我发现了两个简单的测试,我可以不执行认证的舞蹈。

  • 外部用户查找
  • 外部组查找

对于外部用户查找,转到配置➜用户并选择一个用户账户。屏幕加载后,展开登录下的 LDAP 部分,并单击添加登录按钮。

如果一切正常,您会看到一个类似如下的模式窗口:

successful user lookup

一开始没看到那个屏幕。相反,我看到了这个:

failed user lookup

错误“无法连接到 LDAP 服务器。如果这种情况再次出现,请咨询您的管理员。错误代码 49 无效凭据”是一个 LDAP 查找错误。我键入了查找用户的密码。

LDAP 还为每个错误代码返回一个数据代码。要找到这些信息,打开你的 Octopus 服务器日志。

data error code

对于您无法解释的错误,在托管 Octopus Deploy 的同一服务器上使用 LDAP explorer 执行类似的操作。把 Octopus 从等式中去掉,通过 explorer 工具让一切正常工作,然后配置 Octopus Deploy。如果一切都可以通过浏览器工作,而 Octopus Deploy 仍然无法工作,请联系customersuccess@octopus.com寻求更多帮助。

外部组查找类似于外部用户查找。

  • 转到配置➜团队并选择一个团队。
  • 点击按钮添加 LDAP 组并执行搜索。

如果配置正确,您会看到以下消息:

external group lookup successful

如果查找失败,请执行与用户查找相同的故障排除。

正在登录

在上述测试成功之后,尝试下一个测试,使用 LDAP 身份验证提供者登录 Octopus。

我创建了一个测试帐户Professor Octopus,并将其添加到了Developers组。

当我第一次尝试以professor.octopus@devopswalker.local的身份登录时,我得到了这个错误:

UPN Error

将用户名改为professor.octopus如预期的那样有效。这是因为默认配置使用sAMAccountName来匹配。新用户已创建并分配给适当的团队。

Successful sign in

我更喜欢用professor.octopus@devopswalker.local登录。如果您有类似的偏好(或公司政策),将用户过滤器更改为(&(objectClass=person)(userPrincipalName=*))

在我们的测试中,我们注意到使用user@domain得到的结果不太可靠;但是,您的配置可能与我们的测试环境不同。

Updated User Filter

我选择了userPrincipalName,因为完全限定名professor.octopus@devopswalker.local存储在我的域控制器中。您的目录服务器可能不同。

user principal vs user id

结论

与 Active Directory 身份验证提供程序不同,LDAP 身份验证提供程序更灵活,因为 LDAP 更灵活。不过,这种适应性确实使 LDAP 身份验证提供程序变得更加复杂,所以可能需要进行一些尝试和错误来拨入您的设置。我建议设置一个测试实例来测试所有的 LDAP 认证设置。

拨入设置后,LDAP 鉴定提供程序提供了许多好处。可以将 Octopus Linux 容器与 Active Directory 一起使用,也可以根本不用 Active Directory。

如果你正在使用活动目录,我发现 LDAP 提供者比活动目录更容易使用,因为它是一个开放的标准。我不必担心选择 NTLM 或 Kerberos,或者如何管理托管 Octopus/Active Directory/域控制器关系的 Windows 服务器。

如果您在本地 active directory 上有多个用户帐户,每个帐户在 Octopus 中都有不同的权限,那么注销和登录您的计算机进行测试就不再是问题。LDAP 解决了这些问题,还有许多其他问题。

愉快的部署!

PowerShell 所需状态配置(DSC)入门- Octopus 部署

原文:https://octopus.com/blog/getting-started-with-powershell-dsc

Octopus learning how to configure a server with PowerShell DSC

PowerShell DSC 是一项非常棒的技术,可以放在您管理基于 Windows 的服务器的工具箱中。这篇文章是一系列文章的一部分:

我们也有关于使用 PowerShell DSC 和 Octopus Deploy 的文章:


无论您是大型组织还是小型组织,使用云基础架构还是机架式服务器,维护服务器的已知状态都是一项挑战。存在一些第三方解决方案,如 Ansible、Chef 和 Puppet,但它们是基于 Linux 的付费产品。对于 Windows 用户,有一个免费的以 Windows 为中心的选项;PowerShell 所需状态配置(DSC)。在这个 PowerShell DSC 教程中,我将向您展示如何开始使用 PowerShell DSC,并提供一些如何使用它的基本示例。

什么是 PowerShell 期望状态配置(DSC)

PowerShell DSC 是一种基础结构代码(IaC)技术,它使用 PowerShell 创建托管对象格式(MOF)文件,Windows Management Instrumentation(WMI)可以使用这些文件来配置计算机。换句话说,PowerShell DSC 使用 PowerShell 以编程方式配置基于 Windows 的计算机。此外,DSC 可以监控已配置资源的状态,以确保您的计算机保持一致。除了监控,DSC 还可以自动纠正系统的配置,使其始终处于所需的状态。

PowerShell!= PowerShell DSC

如果您曾经参加过 PowerShell 的课程,您的讲师可能会提到 PowerShell DSC,但会掩饰地说这是一门完全不同的课程,或者 PowerShell DSC 还有其他课程。DSC 使用 PowerShell 脚本语言,但相似之处仅此而已。

为什么使用 PowerShell DSC

维护基础设施一致性的一种常用方法是为需要启动的不同类型的服务器创建基本映像。需要网络服务器吗?使用 web 服务器映像。需要数据库服务器吗?使用数据库服务器映像。虽然这无疑缩短了用已知/良好的配置供应资源所花费的时间,但是存在一些固有的问题。

例如,当您的首席信息安全官(CISO)更改了您的 web 服务器允许的协议时会发生什么?当然,您可以修复基本映像,或者重新创建所有 web 服务器,或者编写自动化脚本将新的安全配置应用到现有的服务器,但是重新创建服务器或者编写并测试更新脚本可能会花费大量时间,尤其是在您有数百台服务器的情况下。如果新实施的安全标准破坏了与业务合作伙伴的季度接口,该怎么办?现在你必须撤销所有的工作。

使用 PowerShell DSC,您可以定义您想要的状态,所有新的和现有的服务器都可以选择并实现该状态。如果您必须撤销它,只需更改所需的状态即可恢复。

它是如何工作的?

PowerShell DSC 将使用 PowerShell 配置的组件转换成 MOF 文件,供 WMI 用来配置机器。DSC 可以使用两种方法将配置应用到您的机器上;推拉。您还可以使用自动部署工具(如 Octopus Deploy)创建一种混合方法。

推送方法

推送方法可能是最容易开始的方法。这种方法要求用户通过调用Start-DscConfiguration cmdlet 将期望的状态配置推送到服务器。这具有立即开始应用配置的优点。就自动化而言,这种方法的缺点是,如果服务器离线,它将无法应用新的期望状态。这就是拉方法可能是更好的方法的地方。

拉动方法

顾名思义,Pull 方法通过服务器获取所需的状态配置并应用它。这要求您有一个包含服务器配置的拉服务器。这种方法的缺点是需要额外的服务器来托管配置。然后,需要配置已配置的服务器来轮询“拉”服务器,以确定是否有新的 MOF 文件可用。

入门指南

这篇文章是为初学 DSC 的人设计的,所以我们将使用更简单的推送方法开始。对于我们的场景,我们希望确保服务器的配置包括一些 Windows 特性。我们只使用几个例子:

  • 网络服务器
  • 网络管理工具
  • web-默认-文档

如果您想知道我是从哪里得到这些名字的,请使用Get-WindowsFeature cmdlet 获取列表。您也可以使用通配符作为名称,例如Get-WindowsFeature Web*

简单的配置脚本

对于 DSC,我们使用Configuration关键字来定义配置。在本例中,WindowsFeature是我们正在配置的组件。您可以看到我们定义了三个单独的WindowsFeature组件的实例,每个实例对应一个我们想要配置的组件。每个已配置的实例都需要自己唯一的名称,因为该名称在转换为 MOF 文件时用作关键字。您配置的每个组件都有一个Ensure属性,它的值可以是PresentAbsent。当您想确保组件存在时,您可以指定Present。如果您不想在机器上安装组件,您可以指定Absent。对于这个例子,我们希望确保组件安装在服务器上,所以我们为所有组件指定了Present

在我们完成一个Configuration之后,我们像调用一个函数一样调用它,并提供一个OutputPath,这样 DSC 就知道在哪里放置生成的 MOF 文件。对于这个例子,我们将其命名为WebServerConfiguration。DSC Configuration完成后,我们调用Start-DscConfiguration cmdlet 并提供我们生成的 MOF 文件的路径:

Configuration WebServerConfiguration
{  
  Node "localhost"
  {        
    WindowsFeature WebServer
    {
      Name = "Web-Server"
      Ensure = "Present"
    }

    WindowsFeature ManagementTools
    {
      Name = "Web-Mgmt-Tools"
      Ensure = "Present"
    }

    WindowsFeature DefaultDoc
    {
      Name = "Web-Default-Doc"
      Ensure = "Present"
    }
  }
}

WebServerConfiguration -OutputPath "C:\DscConfiguration"

Start-DscConfiguration -Wait -Verbose -Path "C:\DscConfiguration" 

在您的配置运行之后,您应该会看到类似如下的输出:

分离节点数据并使脚本更加动态

我们这个简单的例子是非常硬编码的,根本不是动态的。借助 PowerShell DSC,我们能够将配置数据从配置本身中分离出来,并使我们的脚本更加动态。毕竟是 PowerShell😃

DSC 配置数据文件只是一组哈希表,可以包含其他哈希表或数组,通常具有 psd1 文件扩展名。DSC 配置数据必须至少有一个名为AllNodes的密钥。参考微软文档了解更多信息。

让我们将三个 Windows 功能的原始列表放入 DSC 配置数据文件中:

@{
  AllNodes = @(
    @{
      NodeName = $env:COMPUTERNAME
      WindowsFeatures = @(
        @{
          Name = "Web-Server"
          Ensure = "Present"
        },
        @{
          Name = "Web-Mgmt-Tools"
          Ensure = "Present"
        },
        @{
          Name = "Web-Default-Doc"
          Ensure = "Present"
        }
      )
    }
  )
} 

通过分离配置数据,我们可以缩短 DSC PowerShell 脚本并使其更加通用:

Configuration WebServerConfiguration
{  
  Node $AllNodes.NodeName
  {        
    # Loop through the defined features
    ForEach($Feature in $Node.WindowsFeatures)
    {
      # Define component
      WindowsFeature $Feature.Name
      {
        Name = $Feature.Name
        Ensure = $Feature.Ensure
      }
    }
  }
}

WebServerConfiguration -OutputPath "C:\DscConfiguration" -ConfigurationData "C:\DscConfiguration\WebServer.psd1"

Start-DscConfiguration -Wait -Verbose -Path "C:\DscConfiguration" 

在我们的新脚本中有两件事需要注意:

  • WebServerConfiguration的调用现在多了一个参数ConfigurationData。这告诉 DSC 包含要加载的配置数据的文件。
  • 我们可以使用点符号引用 DSC 配置数据文件的属性。

检测漂移

如前所述,DSC 可以检测某样东西是否不再处于所需状态。需要注意的是,DSC 只能检测它被告知要关注的变化。使用我们的示例,如果有人安装了 Web-Ftp-Server Windows 特性,我们的 DSC PowerShell 脚本将不会报告任何内容。然而,如果有人删除了 Web-Default-Doc,DSC 将报告该特性不再处于期望的状态。当 DSC 配置不再处于所需状态时,我们称之为漂移。

要运行当前配置的测试,您可以运行Test-DscConfiguration cmdlet。如果配置处于期望的状态,Test-DscConfiguration返回True,如果发生了漂移,则返回False。传递-Detailed参数将返回漂移内外的资源列表:

让我们通过运行以下命令来删除 Web-Default-Doc:

Uninstall-WindowsFeature Web-Default-Doc 

运行Test-DscConfiguration -Detailed:

如您所见,机器已经漂移并识别出[windows feature]Default Doc(we b-Default-Doc)不再处于所需状态!

自动校正漂移

您可以将本地配置管理器(LCM)配置为在检测到漂移时自动更正配置。为此,我们在 DSC PowerShell 脚本中放置了一个LocalConfigurationManager节点,并设置了ConfigurationMode属性。ConfigurationMode可以有三个值之一:

  • ApplyOnly:该设置指示 LCM 应用配置,不做任何其他事情。
  • ApplyAndMonitor:该设置指示 LCM 应用配置并定期运行一致性检查(本质上是Test-DscConfiguration)。一致性检查的默认频率是 15 分钟,可以通过设置ConfigurationModeFrequencyMins属性来覆盖。
  • ApplyAndAutoCorrect:该设置指示 LCM 应用配置并定期运行一致性检查。如果一致性检查返回false,LCM 将重新应用配置,使机器回到所需状态。

为了配置 LCM 自动校正漂移,我们将它设置为ApplyAndAutoCorrect:

Configuration WebServerConfiguration
{  
  Node $AllNodes.NodeName
  {
    # Configure the LCM
    LocalConfigurationManager
    {
      ConfigurationMode = "ApplyAndAutoCorrect"
    }        

    # Loop through the defined features
    ForEach($Feature in $Node.WindowsFeatures)
    {
      # Define component
      WindowsFeature $Feature.Name
      {
        Name = $Feature.Name
        Ensure = $Feature.Ensure
      }
    }
  }
}

WebServerConfiguration -OutputPath "C:\DscConfiguration" -ConfigurationData "C:\DscConfiguration\WebServer.psd1"

Start-DscConfiguration -Wait -Verbose -Path "C:\DscConfiguration" 

现在我们的服务器将自动纠正自己每当漂移被检测到!如果您启用了自动漂移校正,请确保您记录了它;否则,你或你团队中的某个人将会试图找出为什么你刚刚删除的东西又回来了!

摘要

这篇博文教程提供了一些关于如何开始使用 PowerShell DSC 的基本信息,以及如何检测和有选择地自动纠正漂移。关于提到的混合方法的例子,请参考本系列的这篇文章,在这篇文章中,我们将 PowerShell DSC 配置为像应用程序一样进行部署。

Octopus Deploy v3 - Octopus Deploy 的 GitHub 动作中的新功能

原文:https://octopus.com/blog/github-actions-for-octopus-deploy-v3

我们在 2021 年 6 月向 GitHub 市场发布了第一组 GitHub 行动。然后在 2022 年 9 月,我们更新了我们的行动,加入了许多新功能

作为我们针对 Octopus Deploy 的 GitHub 动作的第三次迭代的一部分,我们做了进一步的改进,并添加了 5 个新动作。

亮点包括:

在这篇文章中,我从技术上深入探讨了这次迭代的关键变化,并向您展示了使用新动作的示例。

不再需要 Octopus CLI

消除对 Octopus CLI 的依赖是 GitHub 操作的最大架构变化。

我们的操作不再使用 Octopus CLI 来执行工作。相反,它们直接从 TypeScript 与 Octopus API 交互。这意味着您的工作流启动和执行速度比以前快得多。

您仍然可以使用 Octopus CLI,但是如果您只需要使用我们的其他操作,则不再需要将install-Octopus-CLI-action包含在您的工作流中。

如果您有自己需要的脚本,那么install-octopus-CLI-action仍然可供您使用。

安装 Octopus CLI 操作现在安装基于 Go 的 CLI

我们最近将 CLI 实现从 C#转移到了 Go(关于原因的更多信息,请参见构建 Octopus CLI vNext )。Octopus CLI ( octo)将继续得到支持,直到 2023 年年中。实际上,install-Octopus-CLI-action的 v1 会继续安装 Octopus CLI ( octo)。如果您有使用基于 C#的 CLI 的现有工作流,您可以继续使用此操作的 v1。

install-octopus-CLI-actionv3(或更高版本)只会安装新的基于 Go 的 CLI ( octopus)。如果您正在编写新的工作流,我们强烈建议使用 v3。基于 Go 的 CLI ( octopus)具有基于 C#的 CLI 所没有的新特性和改进,但是,也有一些微小的差异。如有必要,这些 CLI 可以同时使用。

环境变量名

为了安全起见,我们提倡在操作中使用环境变量,而不是 CLI 参数。

我们仍然鼓励您使用环境变量来设置敏感值(即 API 键),但是在新版本的操作中,名称已经改变,因为不再是 Octopus CLI 来选择它们。

价值 旧变量 新变量
Octopus 服务器 URL OCTOPUS_CLI_SERVER OCTOPUS_URL
Octopus API 密钥 OCTOPUS_CLI_API_KEY OCTOPUS_API_KEY
章鱼空间名称 OCTOPUS_SPACE

部署和运行手册运行操作

GitHub Actions for Octopus Deploy v3 为部署和 runbook 运行引入了 3 项新操作:

创建-释放-动作的 v1 中,我们支持来自 Octopus CLI ( octo)的旧的deploy-to参数。不幸的是,这带来了 Octopus CLI ( octo)支持的所有其他与部署相关的开关。这增加了动作参数,使它们变得复杂和混乱。

作为一个例子,--variable参数经常让人出错。它只适用于为部署设置提示变量的值,但是看起来它可以用于在发布创建期间设置项目变量值。

基于这些参数目前存在的问题,我们在 Octopus Deploy v2 的 GitHub Actions 中删除了它们,以消除混淆。我们的目标是我们现在在 v3 中拥有的,用于排队部署(和 runbooks 运行)的独立动作。

Octopus CLI ( octo)也将是否等待部署完成的概念捆绑到同一个命令中。我们也把它分成了自己的动作。乍一看这似乎有些过分,但是当与 GitHub 动作的其他特性结合使用时,它允许更大的灵活性。我们将在下面的例子中详细讨论这一点。

租赁部署与“标准”部署具有不同的语义。首先,它们支持您可以部署到的环境的不同多样性(标准版可以部署到多个环境,租用版只能部署到一个环境)。为了在动作契约中明确这一点,部署-释放-租赁-动作部署-释放-动作是分开的。

虽然这是这些动作的初始版本,但我们决定将它们发布为 v3,以便更容易将这些新动作作为匹配集进行推理。随着时间的推移,版本会再次出现分歧,因为我们会单独对动作进行修补和更新。

创建 Zip 和 NuGet 包的操作

GitHub Actions for Octopus Deploy v3 引入了 2 个新的包创建操作:

Zip 和 NuGet 包是用于分发和部署软件应用程序的归档文件。

  • Zip 是一种广泛使用的归档格式,可以由许多不同的应用程序打开。
  • NuGet 包是专门为与 Microsoft 开发平台一起使用而设计的,用于分发可以轻松集成到中的库和其他资源。NET 应用程序。

Zip 和 NuGet 包通常用于分发软件,因为它们提供了一种方便有效的方式来打包和分发大量文件。通常,我们观察客户使用 Octopus CLI ( octo)通过pack命令生成包。这些动作消除了这种需求,同时通过 GitHub 动作提供了集成的体验。

链接是一种内置功能

许多动作产生输出,以实现动作的链接。输出如下:

行为 输出 描述
create-release-action release_number 创建的发布号(版本)
deploy-release-action server_tasks 带有serverTaskIdenvironmentName的 JSON 对象数组
deploy-release-tenanted-action server_tasks 带有serverTaskIdtenantName的 JSON 对象数组
run-runbook-action server_tasks 带有serverTaskIdenvironmentNametenantName的对象的 JSON 数组
await-task-action completed_successfully 无论任务是否成功完成

在下面的例子中,我们将更详细地展示如何使用 JSON 数组。

await-task-action的输出中,请注意如果任务没有成功完成,操作将失败。然后,如果您想根据部署/运行是否失败和其他失败(比如失去与 Octopus 实例的通信)在工作流中采取不同的行动,您可以使用步骤的结果completed_successfully

常见工作流模式

多合一

一体式是我们看到的使用最广泛的模式,由 CLI 鼓励您做事情的方式驱动。所有行动都是一项工作中的步骤:

- name: Create Zip package 🐙
  id: package
  uses: OctopusDeploy/create-zip-package-action@v3
  with:
    package_id: DemoNetCoreWebAppGHASingleJob
    version: ${{ steps.build.outputs.version }}
    base_path: ${{ steps.build.outputs.output_folder }}
    files: "**/*"
    output_folder: packaged

- uses: actions/upload-artifact@v3
  with:
  name: ${{ steps.package.outputs.package_filename }}
  path: ${{ steps.package.outputs.package_file_path }}

- name: Push a package to Octopus Deploy 🐙
  uses: OctopusDeploy/push-package-action@v3
  with:
    packages: ${{ steps.package.outputs.package_file_path }}

- name: Push build information to Octopus Deploy 🐙
  uses: OctopusDeploy/push-build-information-action@v3
  with:
    version: ${{ steps.build.outputs.version }}
    packages: MyPackage

- name: Create a release in Octopus Deploy 🐙
  uses: OctopusDeploy/create-release-action@v3
  id: "create_release"
  with:
    project: "Pet Shop"
    package_version: ${{ steps.build.outputs.version }}

- name: Deploy the release in Octopus Deploy 🐙
  uses: OctopusDeploy/deploy-release-action@v3
  id: "queue_deployments"
  with:
    project: "Pet Shop"
    release_number: ${{ steps.create_release.outputs.release_number }}
    environments: |
      Development
      Integration

- name: Waiting for 1st deployment in Octopus Deploy 🐙
  uses: OctopusDeploy/await-task-action@v3
  with:
    server_task_id: ${{ fromJson(steps.queue_deployments.outputs.server_tasks)[0].serverTaskId }}

- name: Waiting for 2nd deployment in Octopus Deploy 🐙
  uses: OctopusDeploy/await-task-action@v3
  with:
    server_task_id: ${{ fromJson(steps.queue_deployments.outputs.server_tasks)[1].serverTaskId }} 

GitHub 操作中运行的工作流的输出如下所示:

Single Job

使用这种模式有以下好处:

  • 在工作流文件中,步骤相对容易链接在一起(与您在下一个示例中看到的相比)
  • 适合部署到单个环境/租户

这种模式有以下缺点:

  • 在有多个之后,你不能保证它们排队的顺序
  • 您无法一眼看出哪个任务适用于哪个环境
  • 步骤是连续执行的,所以直到第一次部署的等待完成后,第二次部署的等待才开始

将操作分成多个作业

在本例中,我们展示了一个使用多个作业来协调操作的工作流。它既使用了作业,也使用了 GitHub 动作中一个名为矩阵的特性:

jobs:
  build:
    name: Build and unit test code
    runs-on: ubuntu-latest

    outputs:
      version: ${{ steps.build.outputs.version }}
      artifact_name: ${{ steps.package.outputs.package_filename }}

    steps:
      - uses: actions/checkout@v3

      - name: Setup .NET
        uses: actions/setup-dotnet@v2
        with:
          dotnet-version: 6.0.x

      - name: Run Build 🏗
        id: build
        run: |
          # do whatever you do to build your package. Assume this outputs a version variable and the folder where it produced output

      - name: Create Zip package 🐙
        id: package
        uses: OctopusDeploy/create-zip-package-action@v3
        with:
          package_id: MyPackage
          version: ${{ steps.build.outputs.version }}
          base_path: ${{ steps.build.outputs.output_folder }}
          files: "**/*"
          output_folder: packaged

      - uses: actions/upload-artifact@v3
        with:
          name: ${{ steps.package.outputs.package_filename }}
          path: ${{ steps.package.outputs.package_file_path }}

  push:
    name: Push information to Octopus
    needs: build
    runs-on: ubuntu-latest

    env:
      OCTOPUS_URL: ${{ secrets.OCTOPUS_URL }}
      OCTOPUS_API_KEY: ${{ secrets.OCTOPUS_API_KEY }}
      OCTOPUS_SPACE: "Galaxy"

    steps:
      - uses: actions/download-artifact@v3
        with:
          name: MyPackage.${{ needs.build.outputs.version }}
          path: package

      - name: Push a package to Octopus Deploy 🐙
        uses: OctopusDeploy/push-package-action@v3
        with:
          packages: package/MyPackage.${{ needs.build.outputs.version }}.zip

      - name: Push build information to Octopus Deploy 🐙
        uses: OctopusDeploy/push-build-information-action@v3
        with:
          version: ${{ needs.build.outputs.version }}
          packages: MyPackage

  snapshot:
    name: Snapshot information in Octopus
    needs: [build, push]
    runs-on: ubuntu-latest

    outputs:
      release_number: ${{ steps.create_release.outputs.release_number }}

    env:
      OCTOPUS_URL: ${{ secrets.OCTOPUS_URL }}
      OCTOPUS_API_KEY: ${{ secrets.OCTOPUS_API_KEY }}
      OCTOPUS_SPACE: "Galaxy"

    steps:
      - name: Create a release in Octopus Deploy 🐙
        id: "create_release"
        uses: OctopusDeploy/create-release-action@v3
        with:
          project: "Rockets"
          package_version: ${{ needs.build.outputs.version }}

  deploy:
    name: Deploy snapshot using Octopus
    needs: [build, snapshot]
    runs-on: ubuntu-latest

    env:
      OCTOPUS_URL: ${{ secrets.OCTOPUS_URL }}
      OCTOPUS_API_KEY: ${{ secrets.OCTOPUS_API_KEY }}
      OCTOPUS_SPACE: "Galaxy"

    outputs:
      server_tasks: ${{ steps.queue_deployments.outputs.server_tasks }}

    steps:
      - name: Deploy the release in Octopus Deploy 🐙
        uses: OctopusDeploy/deploy-release-tenanted-action@v3
        id: "queue_deployments"
        with:
          project: "Rockets"
          release_number: ${{ needs.snapshot.outputs.release_number }}
          environment: Development
          tenants: Mars
          tenant_tags: |
            planets/gas giants

  wait:
    needs: deploy
    runs-on: ubuntu-latest
    name: ${{ matrix.deployment.tenantName }}

    env:
      OCTOPUS_URL: ${{ secrets.OCTOPUS_URL }}
      OCTOPUS_API_KEY: ${{ secrets.OCTOPUS_API_KEY }}
      OCTOPUS_SPACE: "Galaxy"

    strategy:
      matrix:
        deployment: ${{ fromJson(needs.deploy.outputs.server_tasks) }}

    steps:
      - name: Waiting for deployment in Octopus Deploy 🐙
        uses: OctopusDeploy/await-task-action@v3
        with:
          server_task_id: ${{ matrix.deployment.serverTaskId }} 

GitHub 操作中运行的工作流的输出如下所示:

Multiple Jobs

使用这种模式有以下好处:

  • 当有多个部署时,很容易确定哪个是哪个
  • 等待任务并行发生(这是 GitHub 动作中矩阵特性发挥作用的时候)
  • 任务出现在图表的可扩展部分,但也作为单独的条目出现在左侧的摘要中(同样使用矩阵)
  • 工作流中的单个作业如果失败,可以重新运行。例如,假设构建了包并推送到 Octopus,但是由于 Octopus 中的配置错误,发布创建失败了。如果您更正了配置,那么您可以从同一点重新运行工作流,而不必重新构建包并再次推送它们(这两种操作都可能是开销很大的操作)

这种模式有以下缺点:

  • 工作流的设置更加复杂——将步骤的输出传递到作业边界需要在 YAML 中做更多的工作
  • 如果只有一次部署,矩阵可能会感觉负担过重

Runbook 运行

执行操作手册类似于部署版本。我们不会在这里提供一个完整的例子,但是我们想指出 action 提供的输出数据的一个特定方面,以及它对矩阵配置的意义。

在前面的示例中,该条目将矩阵作业的名称绑定到来自 JSON 输出数据的租户名称:

name: ${{ matrix.deployment.tenantName }} 

使用 runbook 运行时,输出数据包含tenantNameenvironmentName,因为它允许您一次请求多个这两个值,并为任何与给定环境的给定项目有关联的匹配租户执行。这意味着您的矩阵名称可以这样做,这取决于哪个值对您更重要:

name: ${{ matrix.deployment.tenantName }} - ${{ matrix.deployment.environmentName }} 

或者

name: ${{ matrix.deployment.environmentName }} - ${{ matrix.deployment.tenantName }} 

这很重要,因为在摘要视图中,GitHub Actions UI 会截断长值。将最重要的信息放在最前面会让你一眼就能找到(钻取后你总能看到完整的细节,只需要额外的点击)。

结论

GitHub Actions for Octopus Deploy v3 对 v2 进行了重大改进,阵容中增加了 5 个新动作。这些新操作增强了自动化部署过程、执行任务和创建包的能力。它们还极大地改善了整体用户体验。

我们自己也在使用这些行动,这证明了它们的有效性和可靠性。我们希望这个最新版本通过提供强大的、用户友好的操作来管理您的 GitHub 部署,从而帮助您。

愉快的部署!

宣布 GitHub 对 Octopus Deploy 的操作- Octopus Deploy

原文:https://octopus.com/blog/github-actions-for-octopus-deploy

GitHub actions integrating with Octopus Deploy build

我们已经为 Octopus Deploy 发布了我们的第一个官方 GitHub 动作。这些初始操作涵盖了将 GitHub 构建与 Octopus 中的部署和 runbook 运行相连接的核心集成场景:

我们计划在今年晚些时候添加更多的 GitHub 动作。

在这篇博文中,我演示了如何开始使用 GitHub Actions 将构建工件推送到 Octopus,创建一个版本,并将其部署到开发环境中。

什么是 GitHub Actions?

GitHub Actions 是一个流行的新平台,用于自动化软件开发工作流,如围绕 GitHub 生态系统构建的 CI/CD。

您使用 YAML 配置文件定义您的工作流,并将其存储在您的 Git 存储库中。您可以用称为操作的可重用构建块来构建自动化。工作流在容器中执行,实现可重复且可靠的流程。

下面是一个 GitHub Action job 工作流的例子。NET web 应用程序。GitHub 提供了大多数编程语言和框架的例子。

name: Build

on:
  push:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet publish -o build 
    - name: Test
      run: dotnet test --no-build --verbosity normal 

这个工作流被命名为 build ,每当变更被推送到master分支上的父 Git 仓库时,它就会触发一个单一的作业。准备和执行持续集成构建的步骤包括:

  • 恢复依赖关系
  • 执行构建
  • 运行所有测试

我推荐阅读 GitHub 的文档来了解更多。这篇博文假设您熟悉使用 GitHub 操作构建工作流的基础知识。

Octopus Deploy 的 GitHub 操作入门

为了说明如何为 Octopus 使用新的 GitHub 操作,我们将更新上面的示例构建脚本,以安装 Octopus CLI,打包我们的构建工件并将其推送到 Octopus,然后创建一个发布并将其部署到我们的开发环境中。

完整的工作流文件可以在 GitHub 示例库中找到:

name: Build

on:
  push:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet publish -o build 
    - name: Test
      run: dotnet test --no-build --verbosity normal
    - name: Install Octopus CLI 🐙
      uses: OctopusDeploy/install-octopus-cli-action@v1.1.6
      with:
        version: latest
    - name: Package build artifacts
      run: octo pack --id="RandomQuotes" --format="zip" --version="1.0.${{github.run_number}}" --basePath="/home/runner/work/RandomQuotes/RandomQuotes/build/"
    - name: Push packages to Octopus Deploy 🐙
      uses: OctopusDeploy/push-package-action@v1.0.1
      with:
        api_key: ${{ secrets.OCTOPUS_APIKEY }}
        server: ${{ secrets.OCTOPUS_SERVER }}
        packages: "RandomQuotes.1.0.${{github.run_number}}.zip"
    - name: Create a release in Octopus Deploy 🐙
      uses: OctopusDeploy/create-release-action@v1.0.2
      with:
        api_key: ${{ secrets.OCTOPUS_APIKEY }}
        server: ${{ secrets.OCTOPUS_SERVER }}
        project: "Projects-141"
        deploy_to: "Dev" 

GitHub Actions Secrets for the Octopus Server URL and an API key

注意,我们在这个配置中引用了两个秘密。一个是 Octopus 服务器 URL,另一个是 API 密钥,用于验证和集成 Octopus 实例。在这种情况下,我使用的是一个 Octopus 云实例,然而,如果它是可公开访问的,你也可以连接到一个自托管 Octopus 实例

注意:这是建立一个微软。NET 5 web 应用程序,但也可以是 Spring (Java) web 应用程序或 NodeJS express 服务等。重要的部分是 GitHub 对 Octopus 的操作如何使集成变得容易。

安装 Octopus CLI

 - name: Install Octopus CLI 🐙
      uses: OctopusDeploy/install-octopus-cli-action@v1.1.6
      with:
        version: latest 

要与 Octopus 服务器集成,首先安装 Octopus CLI。这是使用任何其他步骤的先决条件,因为它会使用适当的依赖项引导作业运行器来安装 Octopus CLI。

将构建工件推送到 Octopus

 - name: Pack
      run: octo pack --id="RandomQuotes" --format="zip" --version="1.0.${{github.run_number}}" --basePath="/home/runner/work/RandomQuotes/RandomQuotes/build/" --verbose
    - name: Push a package to Octopus Deploy 🐙
      uses: OctopusDeploy/push-package-action@v1.0.1
      with:
        api_key: ${{ secrets.OCTOPUS_APIKEY }}
        server: ${{ secrets.OCTOPUS_SERVER }}
        packages: "RandomQuotes.1.0.${{github.run_number}}.zip" 

下一步是打包您的构建工件,并将它们推到一个包存储库中。在这种情况下,我们正在推进 Octopus 内置的包存储库,这是一个受欢迎的选择。

打包和推送我的构建工件有两个步骤:

  1. 将我的构建输出打包为 ZIP 文件。
  2. 将包推送到我的 Octopus 实例。

如前所述,我引用了存储在我的存储库配置中的两个秘密。一个是 Octopus 服务器 URL,另一个是我的 GitHub 构建的 API 密钥。

创建一个版本并将其部署到开发环境中

 - name: Create a release in Octopus Deploy 🐙
      uses: OctopusDeploy/create-release-action@v1.0.2
      with:
        api_key: ${{ secrets.OCTOPUS_APIKEY }}
        server: ${{ secrets.OCTOPUS_SERVER }}
        project: "Projects-141"
        deploy_to: "Dev" 

我的构建过程的最后一步是创建我的项目的发布,并将其部署到我的开发环境中。这是一步完成的;我提供了我的项目 ID 和我想要部署到的环境名称。仅此而已。

成功!

Successful GitHub build

如果我们向我们的存储库提交一个 commit,我们可以看到 GitHub 动作的运行及其输出。可能需要几次迭代来修正语法并获得正确的结果,但结果是一个成功的构建。

结论

针对 Octopus Deploy 的 GitHub 操作现已推出。该版本包括安装 Octopus CLI 和将包推送到 Octopus 实例的操作,以及创建和部署版本和执行操作手册的支持。

您现在可以使用 GitHub 操作自动化您的构建,并与 Octopus 集成以满足您所有的部署和 runbook 自动化需求。

我们计划为 Octopus 添加额外的动作。如果你想让我们添加什么,请在评论中告诉我们。

愉快的部署!

使用 GitHub Actions - Octopus Deploy 将包发布到本地 Octopus 实例

原文:https://octopus.com/blog/github-actions-local-runner

今年早些时候,我的同事 Ryan Rousseau 写了一篇关于使用 GitHub Actions 向 Octopus Deploy 发布一个包的博文。在这篇文章中,我将更进一步,发布一个包到一个用自托管 GitHub Actions Runner 部署的本地 Octopus 实例。

GitHub 操作

GitHub Actions 是 GitHub 版本的构建服务器。像许多其他构建工具一样,如 BitBucket PipeLines 和 Azure DevOps,GitHub Actions 使用另一种标记语言(YAML)来定义构建过程,称为工作流。下面是一个 GitHub Actions 工作流 YAML 文件的例子。

该示例构建了 OctoPetShop 示例。NET 核心应用程序,然后将包推送到我们的 Octopus Deploy Samples 实例:

name: .NET Core 

on:
  push:
    branches: [ master ] 
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest 

    steps:
    - uses: actions/checkout@v2
    - name: Set Version
      run: echo "::set-env name=PACKAGE_VERSION::$(date +'%Y.%m.%d').$GITHUB_RUN_NUMBER"
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 2.2.207
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal
    - name: Create artifacts folder
      run: |
        mkdir "$GITHUB_WORKSPACE/artifacts"
        mkdir "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Database"
        mkdir "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Web"
        mkdir "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.ProductService"
        mkdir "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.ShoppingCartService"
    - name: Publish OctoPetShopDatabase
      run: dotnet publish OctopusSamples.OctoPetShop.Database/OctopusSamples.OctoPetShop.Database.csproj --configuration Release --no-restore --output "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Database"
    - name: Publish OctoPetShopWeb
      run: dotnet publish OctopusSamples.OctoPetShop.Web/OctopusSamples.OctoPetShop.Web.csproj --configuration Release --no-restore --output "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Web"
    - name: Publish OctoPetShopProductService
      run: dotnet publish OctopusSamples.OctoPetShop.ProductService/OctopusSamples.OctoPetShop.ProductService.csproj --configuration Release --no-restore --output "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.ProductService"
    - name: Publish OctoPetShopShoppingCartService
      run: dotnet publish OctopusSamples.OctoPetShop.ShoppingCartService/OctopusSamples.OctoPetShop.ShoppingCartService.csproj --configuration Release --no-restore --output "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetshop.ShoppingCartService"
    - name: Install Octopus CLI
      uses: OctopusDeploy/install-octocli@v1
      with:
        version: 7.4.2
    - name: Package OctoPetShopDatabase
      run: |
        octo pack --id="OctoPetShop.Database" --format="Zip" --version="$PACKAGE_VERSION" --basePath="$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Database" --outFolder="$GITHUB_WORKSPACE/artifacts"
    - name: Package OctoPetShopWeb
      run: |
        octo pack --id="OctoPetShop.Web" --format="Zip" --version="$PACKAGE_VERSION" --basePath="$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Web" --outFolder="$GITHUB_WORKSPACE/artifacts"
    - name: Package OctoPetShopProductService
      run: |
        octo pack --id="OctoPetShop.ProductService" --format="Zip" --version="$PACKAGE_VERSION" --basePath="$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.ProductService" --outFolder="$GITHUB_WORKSPACE/artifacts"
    - name: Package OctoPetShopShoppingCartService
      run: |
        octo pack --id="OctoPetShop.ShoppingCartService" --format="Zip" --version="$PACKAGE_VERSION" --basePath="$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetshop.ShoppingCartService" --outFolder="$GITHUB_WORKSPACE/artifacts"
    - name: Push OctoPetShop Database
      run: |
        octo push --package="$GITHUB_WORKSPACE/artifacts/OctoPetShop.Database.$PACKAGE_VERSION.zip" --server="${{ secrets.OCTOPUSSERVERURL }}" --apiKey="${{ secrets.OCTOPUSSERVERAPIKEY }}" --space="${{ secrets.OCTOPUSSERVERSPACE_HYBRID }}"
    - name: Push OctoPetShop Web
      run: |
        octo push --package="$GITHUB_WORKSPACE/artifacts/OctoPetShop.Web.$PACKAGE_VERSION.zip" --server="${{ secrets.OCTOPUSSERVERURL }}" --apiKey="${{ secrets.OCTOPUSSERVERAPIKEY }}" --space="${{ secrets.OCTOPUSSERVERSPACE_HYBRID }}"
    - name: Push OctoPetShop ProductService
      run: |
        octo push --package="$GITHUB_WORKSPACE/artifacts/OctoPetShop.ProductService.$PACKAGE_VERSION.zip" --server="${{ secrets.OCTOPUSSERVERURL }}" --apiKey="${{ secrets.OCTOPUSSERVERAPIKEY }}" --space="${{ secrets.OCTOPUSSERVERSPACE_HYBRID }}"
    - name: Push OctoPetShop ShoppingCartService
      run: |
        octo push --package="$GITHUB_WORKSPACE/artifacts/OctoPetShop.ShoppingCartService.$PACKAGE_VERSION.zip" --server="${{ secrets.OCTOPUSSERVERURL }}" --apiKey="${{ secrets.OCTOPUSSERVERAPIKEY }}" --space="${{ secrets.OCTOPUSSERVERSPACE_HYBRID }}" 

随着 GitHub 托管的 runners 推送到 Octopus Cloud,这个解决方案工作得非常好。然而,如果不穿透防火墙,GitHub 托管的 runners 就无法将包推送到您的自托管 Octopus Deploy 服务器。

本地生成运行程序

GitHub 托管的 runner 预打包了许多功能,但有时您有特定的软件需求,需要能够控制您正在使用的版本,或者需要 runner 能够访问内部资源,如 Octopus Deploy。为了解决这个问题,GitHub 的人开发了本地托管跑步者的功能。本地运行器的工作方式类似于 Octopus 轮询触角,它们伸出 GitHub 动作,而不是 GitHub 动作伸入。

设置跑步者

设置本地跑步者非常简单。感谢 GitHub 让它变得如此简单。创建工作流 YAML 文件后,在 GitHub repo 中进行设置:

在那里,单击操作:

稍微向下滚动以看到添加流道按钮并点击它:

下一个屏幕为您提供了选择本地 runner 架构的选项,然后提供了下载和配置它所必需的命令:

当运行配置命令时,您将被询问一些基本的问题,比如 runner 名称、标签和工作文件夹位置。在这些提示下按 Enter 键接受默认值。

当你完成后,你将有一个本地的跑步者监听工作:

将工作流配置为使用本地流道

在 YAML 中,将工作流配置为使用本地运行器只需一行代码。以上面的 YAML 为例,我们将行runs-on改为本地实例的标签。runs-on的当前值使用单个标签ubuntu-latest。然而,当你需要多个标签时,你必须把它们放在一个数组中,这个数组是用方括号指定的。对于我们的新跑步者,我们希望它使用标签self-hostedlinux。为此,我们将改变:

runs-on: ubuntu-latest 

收件人:

runs-on: [self-hosted, linux] 

将我们的工作流配置为使用本地运行器后,我们现在可以将包推送到 Octopus Deploy 的本地实例。通过对 YAML 文件进行更改,它启动了一个构建,我可以看到我的本地运行人员通过点击Actions已经获得了它:

在我的虚拟机上,我看到正在运行作业的消息:

 √ Connected to GitHub

2020-06-09 23:47:30Z: Listening for Jobs
2020-06-10 00:27:20Z: Running job: build 

检查日志输出,我们可以看到构建的版本号是 2020.06.10.6:

在我的本地 Octopus 部署实例上,我发现这已经被推了:

结论

这篇文章演示了一种使用云构建服务器通过使用本地构建运行器或代理将包推送到自托管 Octopus Deploy 实例的方法。虽然这里关注的是 GitHub 动作,但同样的想法也可以扩展到 TeamCity、Jenkins 或 Azure DevOps。

GitHub 代码空间私人预览版——Octopus Deploy

原文:https://octopus.com/blog/github-codespaces-private-preview

A look at GitHub Codespaces private preview

有许多源代码控制系统,但让我们面对它,GitHub 可以说是最受欢迎的。

GitHub 最近通过引入 GitHub Actions 加强了他们在 CI/CD 领域的游戏,在微软 Build 2020 上,他们引入了 GitHub Codespaces

GitHub Codespaces 就像 Visual Studio Codespaces,它是 UI 中的 VS 代码,除了 GitHub Codespaces,它内置于 GitHub 中,为您的所有开发需求提供一站式服务。

在这篇博文中,我将介绍一下私人测试版,以及如果你已经被测试版项目接受,该如何使用它。

先决条件

要跟进,您需要:

  • GitHub Codespaces 的私人测试邀请。如果你还没有,你可以请求提前访问
  • GitHub 账户。
  • 至少一个存储库。

为什么选择 GitHub Codespaces?

拥有如此多的开发工具和编写代码的方式有一个大问题;根本没有一个集中的地方来存储您想要的特定配置。以 VS 代码为例,假设您在几台不同的机器上工作,或者您正在与一个队友进行结对编程,要处理相同的代码并使其看起来完全相同,您需要几样东西:

  • 扩展。
  • 登录服务,比如从 VS 代码登录 Azure。
  • 主题。
  • settings.json中的设置。
  • 运行时。
  • 棉绒。

你知道这是怎么回事了。管理起来可能会很麻烦。

有了代码空间这样的东西,你就不用担心这个了。无论您从何处登录,所有设置和配置都在一个位置。

代码空间为您的特定开发需求提供了一个集中的位置。

设置代码空间

如果私人测试版是活跃的,你会在顶部任务栏上看到一个名为 Codespaces 的新图标。它应该是自动添加的,但您可能需要注销 GitHub,然后重新登录。

  1. 登录 GitHub,点击代码空间图标。
  2. 点击绿色的新代码空间按钮。
  3. 您将看到两个选项:

选择您想要在代码空间中打开的存储库。代码将显示在代码空间中,因此任何东西都可以工作。

  1. 点击绿色的创建代码空间按钮。

代码空间将会打开,您将能够像在桌面上使用 VS 代码一样看到来自存储库的代码:

Am open repository in Codespaces

首先看看扩展

从扩展的角度来看,什么都没有改变。您通常在 VS 代码中使用的任何扩展都可以在代码空间中使用。

  1. 打开“扩展”选项卡:
  2. 搜索扩展名,例如 Golang:

Go extensions in Codespaces

GitHub 代码空间中的设置

令人惊讶的是,代码空间中的设置与 VS 代码中的设置几乎相同,这使得转换变得熟悉而平稳:

  1. 点击左下角的设置图标。
  2. 从那里,您可以配置环境、用户环境和远程代码空间环境:

Codespace settings

结论

尽管 GitHub Codespaces 仍处于私人预览阶段,但很明显它是一个未来的工具。开发者没有必要离开 GitHub。他们现在可以在一个地方存储和编写代码。

GitHub Feeds - Octopus 部署

原文:https://octopus.com/blog/github-feed

Blog Image

给你 GitHub

有时你只是想部署你的应用程序,但它不需要一个构建步骤。它可能是一个存储库,你可以在那里存储你的云形成模板,一堆在 Octopus 中运行的脚本,或者一个你用 nodejs 这样的解释语言运行的简单应用程序。2018.3.0中提供的 GitHub feed 类型为您在 Octopus 部署期间访问资源提供了一种新的方式。没错,你没听错,我们现在支持使用 GitHub 作为提要源。

Good News

我们现在支持使用 GitHub 作为提要源。

这种新的提要类型允许 Octopus 直接从 GitHub 部署文件,而不需要任何额外的中间构建步骤。这意味着不再打包您的脚本,以便它们可以在 Octopus 中使用,并且在源代码控制中存储您的部分部署过程时有更好的体验。标记、推送,然后直接从 Octopus 部署,无需构建服务器

来龙去脉

从部署资源的角度来看,从一个包存储库中构建工件在许多方面看起来与源代码控制中的代码是完全不同的概念。然而,通过查看我们如何对 NuGet 包的各个部分建模,我们可以看到 GitHub“package”包如何被建模以适应 Octopus 生态系统的一些相似之处。

进料类型 源 Uri 包裹 版本
NuGet 任何支持 NuGet v2 或 v3 api 的提要(例如https://api.nuget.org/v3/index.json 包的名称,通常在.nuspec文件中定义。(如Octopus.Clients) 包的不同实例通过具有 semver 2 版本格式的.nuspec文件进行版本控制。
GitHub 任何支持 v3 api 的 GitHub 端点。这可以是标准的公共端点或私有 GitHub 安装。(例如https://api.github.com) 完整的存储库身份,包括所有者。(如OctopusDeploy/Calamari) 一个可以被解析为 semver 2 版本的独特标签。如果该标签存在一个版本,那么这些版本说明将在 Octopus 中显示在版本详细信息中该包的旁边。

构建这种提要类型是为了提供一种简单的方法,将 GitHub 资源作为包处理,而不会增加处理分支和提交的复杂性。因为这些概念不能很好地映射到现有的 Octopus 概念,所以决定简单地读取和解析存储库上的标签,并在识别要部署的特定包时将它们视为 Octopus 使用的版本。顺便提一下,由于 GitHub 提供了基于标签的可下载 zip 包,这提供了一种简单的机制来检索所需的文件,然后像简单的 zip 包一样集成到现有的 Octopus 部署流程中。在这篇文章的最后还提出了一些进一步的观点,概述了围绕作为提要类型的 Git 的一些未来想法。同样值得指出的是,目前只有源文件被 Octopus 部署。其他链接到 GitHub 发行版的二进制文件目前还没有包括在内,但是这在将来可能会改变。

它看起来像什么?

不需要构建过程的“包”的一个经典例子是存储在 Octopus 之外的版本控制中的脚本,它作为部署的一部分运行。接下来的几节看看我们如何配置一个部署来执行来自OctopusDeploy/AcmeScripts GitHub 存储库的脚本。

设置馈送

首先,让我们看看如何在 Octopus 中创建一个 GitHub 外部提要。

GitHub Feed

如您所见,我们提供了设置个人访问令牌的能力,而不仅仅是用户名和密码。这允许你创建 GitHub 称之为的机器用户,它实际上是组织中的 GitHub 用户,用于这些种类的自动化任务。

当访问 GitHub 端点时,为 Octopus 提供一个认证选项是很重要的,因为匿名请求被 GitHub 限制为比认证请求低得多的值。

使用软件包

此提要中的“包”被视为与任何其他包完全相同。在这个场景中,我们将选择一个脚本步骤,并从一个包中获取我们的RunMe.ps1脚本。packageID 是标识我们的AcmeScripts存储库的完整的Octopus/AcmeScripts名称。

Script Step

请注意,当您搜索软件包时,如果您省略了/字符,它将在所有存储库中搜索(您的帐户可以访问的)。添加不带库的/将列出该所有者的所有包,添加库名将搜索该所有者的库。

Search

添加 GitHub 标签

我们现在已经配置了 Octopus,我们只需要将 PowerShell 脚本添加到我们的 GitHub 存储库OctopusDeploy/AcmeScripts中,它将在我们的项目中执行。

echo Write-Host Hello World > RunMe.ps1
git add RunMe.ps1
git commit -m "Ready To Run"
git tag 0.0.1
git push
git push --tags 

为了更好地衡量,我们还将通过 GitHub 门户网站为这个标签添加一些发行说明。

GitHub Release

创建版本

由于我们目前不支持从外部源自动创建发布(注意这个空间),Octopus 不知道我们刚刚推了这个新标签,直到我们创建了一个新的发布。

Octopus Release

在部署时,Octopus 将通过 GitHub API 从标记的 commit 下载源代码。

从这一点上来说,在整个部署过程中,被有效地视为一个典型的 zip 文件,允许它被提取、传输或用作脚本和模板的源。

Task Result

看马,没身材!

未来计划

值得再次重申的是,这种新的提要类型完全是建立在标签和发布之上的。当使用新的 feed 类型时,分支、提交和 heads 这样的概念并不直接相关。您可以间接地处理分支,方法是在这些分支中适当地标记您的提交(记住,一旦您将这些提交合并回master分支,那么标记可能会引用一个属于master分支的提交)。有计划正在进行中,以提供真正的 Git-as-a-feed 支持,其中提交\分支将被更优先地对待,但是决定保持这个 GitHub feed 工作独立。

上面提到的另一点是 GitHub 缺少触发发布和部署的钩子。由于我们许多客户网络的性质,从外部世界打电话并不总是可用的。因此,我们将考虑其他更实用的机制来支持来自外部提要的 ARC。

GitHib 作为包装饲料

我们对这种新的饲料类型给八达通用户带来的机会感到兴奋。使用 GitHub 作为一些部署依赖项的来源将有助于简化您的 CI 管道,并允许更好的版本控制,而无需花费多余的精力和时间来打包那些并不真正需要打包的资源。请让我们知道您对这一新方向的想法,以及它如何在您的部署过程中派上用场。

NoBuild

将 GitHub NuGet 注册表配置为外部 feed - Octopus 部署

原文:https://octopus.com/blog/github-nuget-external-feed

GitHub 已经不仅仅是一个源代码库。GitHub 的人们已经开发了一些功能,比如他们的构建服务器产品、GitHub 操作和问题跟踪。他们甚至开发了 GitHub 包,这是一个与 Docker 容器、npm、Maven、NuGet 包和 RubyGems 兼容的注册表。

在这篇文章中,我将向您展示如何配置 GitHub Actions 作业来将. NET 核心 NuGet 包推入注册表,并将注册表连接到 Octopus Deploy,作为一个外部提要

正在配置。网络核心应用

要将包推送到 GitHub 包,需要在.csproj文件中指定RepositoryUrl元素。当打包并将包推送到注册表时,dotnet命令会使用这些信息。以下是 OctoPetShop 数据库项目的一个示例:

八分之一样本。OctoPetShop.Database.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
    <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
    <RepositoryUrl>https://github.com/OctopusSamples/OctoPetShop.git</RepositoryUrl>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="dbup" Version="4.2.0" />
  </ItemGroup>

  <ItemGroup>
    <EmbeddedResource Include="scripts/*.sql" />
  </ItemGroup>

  <ItemGroup>
    <None Update="deploy.sh" CopyToOutputDirectory="PreserveNewest" />
    <None Update="deploy.ps1" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>

</Project> 

配置 GitHub 操作构建

除了构建应用程序,GitHub Actions 作业还需要执行以下活动:

  • 将应用程序组件打包到 NuGet 包中
  • 添加 NuGet 存储库源
  • 将包推入包注册表

所有这些命令都可以在。NET CLI。

将应用程序打包到 NuGet 包中

您可以通过多种方式将应用程序打包到 NuGet 包中。这篇文章使用了内置在。NET CLI。

要设置包版本,请使用 MSBuild 语法-p:PackageVersion $PACKAGE_VERSION。变量$PACKAGE_VERSION在之前的 GitHub Actions YAML 中已经声明过了(参见我们的 OctoPetShop 示例了解整个过程)。以下是显示 OctoPetShop 数据库项目打包的构建摘录:

 - name: Pack OctoPetShopDatabase
      run: |
        dotnet pack OctopusSamples.OctoPetShop.Database/OctopusSamples.OctoPetShop.Database.csproj --configuration Release --output "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Database" -p:PackageVersion=$PACKAGE_VERSION 

添加 NuGet 存储库源

要定位 GitHub 包注册表,您需要添加注册表作为源。source URL 的格式如下:https://nuget.pkg.github.com/YourGitHubUsernameOrOrganizationName/index.json

您还需要指定具有足够权限的凭据,以便将包推送到注册表。您可以创建个人访问令牌(PAT)或使用 GitHub ActionsGITHUB_TOKEN中的内置密码。

 - name: Add source
      run: |
        dotnet nuget add source "https://nuget.pkg.github.com/OctopusSamples/index.json" --username OctopusSamples --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github 

将包推入包注册表

您还需要为push命令指定凭证,并且您还可以使用GITHUB_TOKEN作为--api-key参数:

 - name: Push packages to GitHub Packages NuGet feed
      run: |
        dotnet nuget push "$GITHUB_WORKSPACE/artifacts/OctopusSamples.OctoPetShop.Database/OctopusSamples.OctoPetShop.Database.$PACKAGE_VERSION.nupkg"  --api-key ${{ secrets.GITHUB_TOKEN }} --source "github" 

推送完包后,它们应该出现在 GitHub 项目的部分。

GitHub project with Packages section highlighted

将 GitHub 包注册表配置为外部提要

GitHub 包注册中心需要认证来拉包。在配置提要之前,首先需要在 GitHub 中创建一个 PAT。

在 GitHub 中创建 PAT

创建 PAT 相对简单。首先,点击右上角的个人资料,然后选择设置

GitHub project with Settings highlighted

滚动至左侧菜单底部,点击开发者设置

GitHub project with Developer Settings highlighted

点击个人访问令牌,然后生成新令牌

GitHub project with Personal access tokens and Generate new token highlighted

给令牌一个描述,选择一个到期时间,并分配read:packages权限。点击生成令牌

New personal access token screen

复制新令牌并将其保存在安全的位置。

创建外部源

要创建外部进给,点击,然后点击外部进给,然后点击添加进给

【T2 Octopus dashboard with External Feeds highlighted

填写饲料表格:

  • 进给类型 : NuGet Feed
  • 名称:输入描述性名称
  • 网址 : https://nuget.pkg.github.com/YourGitHubUsernameOrOrganizationName/index.json
  • 凭证
    • 用户名:令牌的用户名
    • 密码:您之前生成的 PAT

External Feeds in Octopus Deploy

点击保存并测试。输入您用自己的版本创建的包的部分名称,以确保 feed 正常工作。

结论

在这篇文章中,我演示了如何使用。NET CLI 在 GitHub Actions 中,并将包推送到 GitHub Packages 注册表中。然后,我向您展示了如何在 Octopus Deploy 中将注册中心配置为外部提要。

你可能也会对我关于使用 GitLab feeds 和 Octopus Deploy 的帖子感兴趣。

愉快的部署!

GitHub 的操作与传统的构建服务器不同——Octopus Deploy

原文:https://octopus.com/blog/githubactions-different-build-servers

GitHub Actions 对于自动化和持续集成(CI)领域来说相对较新。GitHub Actions 提供“CI 即服务”,与传统的竞争平台有许多不同之处。

在这篇文章中,我们将探讨 GitHub 动作和传统构建服务器之间的区别。我们还要看看 GitHub Actions 是否是构建和测试代码的合适选择。

GitHub Actions CI 已经内置在 GitHub 中

GitHub Actions 相对于传统 CI 平台的最大优势之一就是它的交付。GitHub Actions 是“CI 即服务”,已经包含在所有客户的每个存储库中(稍后将详细介绍定价)。

简单来说:如果你有一个 GitHub 的账户,你可以使用 GitHub 的动作。

如果你喜欢把所有的工作都放在一个地方,不用担心太多的移动部件,这就很有吸引力。

如果你不使用 GitHub,动作不是一个选项

您使用 GitHub 动作的能力取决于您或您的公司使用 GitHub。如果您使用其他代码库,或者您在本地托管您的代码,GitHub Actions 不是一个选项。

然而,值得一提的是,其他代码库服务,如 GitLab 和 BitBucket,也发布了自己的 CI 服务。如果其他服务更合你的口味,你可能还有其他选择。

CI 不需要硬件(除非您愿意)

默认情况下,动作使用“跑步者”来完成工作流的作业。运行者是 GitHub 托管的虚拟机,使用 Windows、Linux 和 MacOS。这意味着您不需要维护自己的基础架构来执行 CI。不需要容纳硬件,不需要维护操作系统,不需要安装、更新或修补。GitHub 会为您处理这一切。

如果你需要 CI 的特定设置,而 GitHub 的跑步者不能胜任这项任务,或者你想要更多的控制,你也可以自己主持你自己的跑步者

许可和定价

传统的构建服务器通常是开源的,或者根据用户或实例的数量根据您的需求进行扩展。然而,CI 即服务意味着提供商可以在定价模式上有所创新。

例如,GitHub Actions采用现收现付的方式,以运行时间来衡量所有工作。

所有用户每月都可以获得免费的工作分钟数和存储空间,其数量随您的计划而定。如果你超过了你的免费分钟数,你将被按分钟计费,并根据跑步者工作所需的操作系统进行加权。

GitHub Actions 对那些拥有公共回购或使用自己托管的跑步者完全免费。

github 操作如何与事件和工作流自动化

GitHub Actions 使用您的 GitHub repo 中的活动(或外部事件,如果您使用'repository dispatch event' web hook)来触发工作流。您可以选择用单个或多个事件启动工作流,使用计划,或手动启动它们。

工作流的结构如下:

  • 每个工作流都包含作业
  • 每个作业都有在自己的运行器上执行的步骤
  • 步骤是每个作业中的单个操作

例如,您可以创建一个工作流:

  1. 每当有人创建拉请求时,测试您的代码
  2. 成功测试后打包代码,并将其推送到您的部署工具

在这种情况下,工作流将有 2 个单独的作业。如果对作业 1 的测试成功,将触发作业 2。这两项工作都有自己的步骤,并在单独、干净的滑道上运行。

感谢 GitHub Marketplace ,你可能根本不需要创建工作流。虽然不是 GitHub Actions 独有的,但是有数以千计的社区制作的动作和工作流可以用来实现您的需求。

Octopus 还创建了一个工具来帮助你构建 GitHub Actions 工作流程。如果您不确定从哪里开始,请尝试一下。

有关 GitHub 操作中工作流的更多信息,请查看:

如何安装操作

与您在大多数其他 CI 平台中添加和管理插件的方式相比,安装操作也有所不同。

例如,在 Jenkins,它们几乎是模块化的迷你应用程序。使用安装。hpi 包,一个 Jenkins 插件可以极大地影响外观、感觉和你在 Jenkins 实例中看到的选项。

在 GitHub 中安装动作包括从动作的 marketplace 页面复制代码,并将其粘贴到您的存储库中。yml 文件。动作不提供可见的变化,而是只提供新的功能,由动作自身的 repo 中的事件触发。

结论

这篇文章讨论了 GitHub Actions(以及 CI 即服务的概念)和传统 CI 平台之间的主要区别。

GitHub Actions 是一个理想的解决方案,如果你:

  • 是 CI 概念的新成员
  • 不想花时间设置和维护专用的构建服务器
  • 不想或不需要在本地托管您的硬件或数据
  • 像你所有的工具一样放在一个地方
  • 已经使用 GitHub

您还可以了解更多关于使用 GitHub 构建和使用 Octopus 部署的信息,并在 GitHub Marketplace 中使用我们的验证操作。

愉快的部署!

使用 GitHub Actions - Octopus Deploy 构建 Docker 映像并将其发布到 ECR

原文:https://octopus.com/blog/githubactions-docker-ecr

GitHub Actions 使用工作流,因此您可以在任何 GitHub 存储库中包含 DevOps 流程。GitHub Actions 允许构建存储库在部署过程中与各种服务进行交互。通常,会构建一个代码库,并将其推入容器注册中心,以便以后进行部署。

在这篇文章中,我将向您展示如何使用 GitHub Actions 构建 Octopus Deploy underwater app 并将其推送到亚马逊弹性容器注册中心(ECR)。

先决条件

要跟进,您需要:

  • 亚马逊网络服务(AWS)帐户
  • GitHub 账户

这个帖子使用了 Octopus 水下应用库。您可以派生存储库并跟随它。或者,github-ecr 分支包含完成本文步骤所需的模板文件。你必须用你自己的价值观来代替一些价值观,但是我已经在这篇文章中列出了我的价值观作为参考。

亚马逊网络服务设置

要为 GitHub 操作设置 AWS,您需要创建一个访问键和一个 ECR 存储库来存储图像。

要创建访问密钥,请转到亚马逊控制台,然后 IAM ,然后用户[your user],然后安全凭证,然后创建访问密钥

您的浏览器下载一个包含访问密钥 ID 和秘密访问密钥的文件。这些值在 GitHub 中用于向 Amazon 认证。

要创建存储库,请转到亚马逊控制台,然后转到 ECR ,然后转到创建存储库

您需要为发布的每个图像建立一个图像存储库。给存储库起一个您想让图像起的名字。

你会在亚马逊 ECR 下看到你的仓库,然后是仓库。记下它所在的区域,在 URI 场。

ECR Repository

GitHub 设置

在本文中,您将构建 Octopus Deploy underwater app 存储库,并将其推送到 Amazon ECR。你可以在以后的博客文章中使用这些图片。

https://github.com/terence-octo/octopus-underwater-app-docker分叉存储库。

进入设置,然后秘密,然后新储存库秘密

  • REPO_NAME -您创建的 AWS ECR 存储库的名称
  • AWS_ACCESS_KEY_ID -之前的访问密钥 ID
  • AWS_SECRET_ACCESS_KEY -之前的秘密访问密钥

您需要在存储库中创建一个工作流文件。GitHub Actions 工作流包含对代码库执行操作的说明。这些是社区维护的步骤。几个预构建的步骤模板允许您在代码存储库上执行许多不同的任务。在本例中,您使用一个步骤模板来构建代码并将其推送到 AWS ECR 存储库。

在。根文件夹的 github/workflows 目录。将以下代码粘贴到 main.yml 文件中:

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

name: AWS ECR push

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-2

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

    - name: Build, tag, and push the image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: ${{ secrets.REPO_NAME }}
        IMAGE_TAG: latest
      run: |
        # Build a docker container and push it to ECR 
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        echo "Pushing image to ECR..."
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" 

GitHub 通过在主分支上使用 push 或 pull 请求来启动一个动作。这些步骤包括检查代码、认证并登录 AWS,然后构建、标记并把图像推送到 Amazon ECR。类似的 step 模板可以推广到其他云存储库,如谷歌或微软。

提交您的更改,转到操作选项卡,并单击您的提交消息的标题。您可以看到工作流程完成时的各个阶段。

GitHub Actions Success

去你的 Amazon ECR 仓库查看图片。Octopus Deploy 现在可以将这个映像部署到部署目标。

ECR Success

结论

在本文中,您设置了一个 GitHub Actions 工作流来构建一个图像并将其推送到 Amazon ECR。像 Octopus 这样的部署工具可以在稍后的部署阶段使用这个映像将 web 应用程序部署到服务中。GitHub Actions 允许代码库成为部署过程的一部分,而无需额外的工作。GitHub Actions 有几个模板,开发人员可以使用它们来执行其他部署任务。

在下一篇文章中,我们将获取 ECR 中的图像并部署 web 应用程序

接下来,我们将使用 GitHub Actions 和 Octopus Deploy 将一个 web 应用程序部署到亚马逊 EKS

试用我们免费的 GitHub Actions 工作流工具来帮助您快速为您的 GitHub Actions 部署生成可定制的工作流。

您还可以了解更多关于使用 GitHub 构建和使用 Octopus 部署的信息,并在 GitHub Marketplace 中使用我们的验证行动。

观看我们的 GitHub 行动网络研讨会

https://www.youtube.com/embed/gLkAs_Cy5t4

VIDEO

阅读我们的持续集成系列的其余部分。

愉快的部署!

在 GitHub Actions - Octopus Deploy 中运行端到端测试

原文:https://octopus.com/blog/githubactions-running-endtoend-tests

GitHub Actions 拥有一个大型的高质量第三方操作生态系统,以及对在 Docker 容器中执行构建步骤的内置支持。这意味着很容易将端到端测试作为工作流的一部分来运行,通常只需要一个步骤来运行具有所有必需依赖项的测试工具。

在这篇文章中,我将向您展示如何作为 GitHub Actions 工作流的一部分,用 Cypress 运行浏览器测试,用 Postman 运行 API 测试。

入门指南

GitHub Actions 是一个托管服务,所以你只需要一个 GitHub 帐户就可以开始了。所有其他依赖项,如软件开发工具包(SDK)或测试工具,都由测试平台发布的 Docker 镜像或 GitHub 动作提供。

用 Cypress 运行浏览器测试

Cypress 是一个浏览器自动化工具,可以让你像终端用户一样与网页互动,比如点击按钮和链接,填写表格,滚动页面。您还可以验证页面的内容,以确保显示正确的结果。

Cypress 文档提供了一个第一次测试的例子,它已经被保存到JUnit-Cypress-test GitHub repo中。测试如下所示:

describe('My First Test', () => {
  it('Does not do much!', () => {
    expect(true).to.equal(true)
  })
}) 

该测试被配置为在cypress.json文件中生成一个 JUnit 报告文件:

{
  "reporter": "junit",
   "reporterOptions": {
      "mochaFile": "cypress/results/results.xml",
      "toConsole": true
   }
} 

下面的工作流文件使用 Cypress GitHub 动作执行这个测试,将生成的视频文件保存为工件,并处理测试结果。您可以在JUnit-cypress-test repository中找到这个工作流的例子:

name: Cypress

on:
  push:
  workflow_dispatch:

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Cypress run
        uses: cypress-io/github-action@v2

      - name: Save video
        uses: actions/upload-artifact@v2
        with:
          name: sample_spec.js.mp4
          path: cypress/videos/sample_spec.js.mp4

      - name: Report
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Cypress Tests
          path: cypress/results/results.xml
          reporter: java-junit
          fail-on-error: true 

调用官方的 Cypress GitHub 动作来执行默认选项的测试:

 - name: Cypress run
        uses: cypress-io/github-action@v2 

Cypress 生成一个视频文件,在测试运行时捕获浏览器。您将视频文件存储为工件,以便在工作流程完成后下载和查看:

 - name: Save video
        uses: actions/upload-artifact@v2
        with:
          name: sample_spec.js.mp4
          path: cypress/videos/sample_spec.js.mp4 

测试结果由dorny/test-reporter动作处理。

请注意,test-reporter 能够处理 Mocha JSON 文件,而 Cypress 使用 Mocha 进行报告,因此一个更惯用的解决方案是让 Cypress 生成 Mocha JSON 报告。不幸的是,Cypress 中有一个错误,使得 JSON reporter 无法将结果保存为文件。在此问题解决之前,生成 JUnit 报告文件是一种有用的变通方法:

 - name: Report
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Cypress Tests
          path: cypress/results/results.xml
          reporter: java-junit
          fail-on-error: true 

以下是测试结果:

Cypress results

视频文件工件列在摘要页面中:

Artifacts

并非所有测试平台都提供 GitHub 动作,在这种情况下,您可以针对标准 Docker 映像执行步骤。这将在下一节中演示。

用 Newman 运行 API 测试

与 Cypress 不同, Postman 不提供官方的 GitHub 动作。但是,您可以在工作流程中直接使用 postman/newman Docker 图像。您可以在JUnit-Newman-test repository中找到工作流的示例:

name: Cypress

on:
  push:
  workflow_dispatch:

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Run Newman        
        uses: docker://postman/newman:latest
        with:
          args: run GitHubTree.json --reporters cli,junit --reporter-junit-export results.xml

      - name: Report
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Cypress Tests
          path: results.xml
          reporter: java-junit
          fail-on-error: true 

步骤的uses属性可以是已发布动作的名称,也可以直接引用 Docker 图像。在这个例子中,您运行 postman/newman docker 图像,用with.args参数定义命令行参数:

 - name: Run Newman       
        uses: docker://postman/newman:latest
        with:
          args: run GitHubTree.json --reporters cli,junit --reporter-junit-export results.xml 

产生的 JUnit 报告文件然后由dorny/test-reporter动作处理:

 - name: Report
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Cypress Tests
          path: results.xml
          reporter: java-junit
          fail-on-error: true 

以下是测试结果:

Newman test results

在后台,GitHub Actions 使用大量与工作流相关的标准环境变量和卷挂载来执行所提供的 Docker 映像,这些卷挂载允许 Docker 容器在主文件系统上持久化更改(如报告文件)。

以下是在 Docker 映像中执行步骤的命令示例:

/usr/bin/docker run --name postmannewmanlatest_fefcec --label f88420 --workdir /github/workspace --rm -e INPUT_ARGS -e HOME -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_OS -e RUNNER_NAME -e RUNNER_TOOL_CACHE -e RUNNER_TEMP -e RUNNER_WORKSPACE -e ACTIONS_RUNTIME_URL -e ACTIONS_RUNTIME_TOKEN -e ACTIONS_CACHE_URL -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" -v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" -v "/home/runner/work/junit-newman-test/junit-newman-test":"/github/workspace" postman/newman:latest run GitHubTree.json --reporters cli,junit --reporter-junit-export results.xml 

这是一个复杂的命令,但是有一些我们感兴趣的参数。

-e参数定义了容器的环境变量。您可以看到暴露了许多工作流环境变量。

--workdir /github/workspace参数覆盖 Docker 容器的工作目录,而-v "/home/runner/work/junit-newman-test/junit-newman-test":"/github/workspace"参数将工作流工作区挂载到容器内的/github/workspace目录。这样做的效果是在 Docker 容器中挂载工作目录,从而公开签出的文件,并允许任何新创建的文件在容器关闭后保持不变:

Docker image command

因为每个主要的测试工具都提供了一个受支持的 Docker 映像,所以您用来运行 Newman 的过程可以用来运行大多数其他测试平台。

结论

GitHub Actions 在开发人员中被广泛采用,许多平台都支持在工作流中使用的操作。对于那些没有合适的动作可用的情况,GitHub Actions 提供了一种简单的方法来执行标准 Docker 图像作为工作流的一部分。

在这篇文章中,您学习了如何运行 Cypress 动作来执行基于浏览器的测试,以及如何运行 Newman Docker 映像来执行 API 测试。

查看我们关于在 GitHub Actions 中测试的另一个帖子:

试用我们免费的 GitHub Actions 工作流工具,帮助您快速为 GitHub Actions 部署生成可定制的工作流。

您还可以了解更多关于使用 GitHub 构建和使用 Octopus 部署的信息,并在 GitHub Marketplace 中使用我们的验证操作。

愉快的部署!

在 GitHub Actions - Octopus Deploy 中运行单元测试

原文:https://octopus.com/blog/githubactions-running-unit-tests

在典型的开发工作流中,用单元测试验证代码变更是一个关键的过程。GitHub Actions 提供了许多自定义操作来收集和处理测试结果,允许开发人员浏览结果、调试失败的测试并生成报告。

在这篇文章中,我将向您展示如何将单元测试添加到 GitHub Actions 工作流中,并配置自定义操作来处理结果。

入门指南

GitHub Actions 是一个托管服务,所以你需要的只是一个 GitHub 帐户。所有其他依赖项,如软件开发工具包(SDK),都是在 GitHub Actions 工作流执行期间安装的。

选择操作

GitHub Actions 非常依赖社区贡献的第三方行动。快速的 Google 搜索显示了至少六个处理单元测试结果的操作,包括:

要缩小选择范围,您需要考虑以下功能:

  • 行动支持你的测试框架吗?例如,有些动作只处理 JUnit 测试结果,而其他动作则包括其他格式,如 TRX。
  • 该操作是否允许您基于失败测试的存在使工作流失败?
  • 该操作是否用测试结果的细节注释了源代码?
  • 该行动是否会生成有用的报告?
  • 项目有几个明星?

经过一些反复试验,我决定采用测试报告者动作,这将在本文中演示。

Java 中的单元测试

下面显示的工作流文件使用 Maven 运行测试,并使用 test-reporter 操作处理结果:

name: Java

on:
  push:
  workflow_dispatch:

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Set up JDK 1.11
        uses: actions/setup-java@v2
        with:
          java-version: '11'
          distribution: 'adopt'

      - name: Build
        run: mvn --batch-mode -DskipTests package

      - name: Test
        run: mvn --batch-mode -Dmaven.test.failure.ignore=true test

      - name: Report
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Maven Tests
          path: target/surefire-reports/*.xml
          reporter: java-junit
          fail-on-error: true 

BuildTestReport步骤对测试过程很重要。

您从构建应用程序开始,但是跳过测试:

 - name: Build
        run: mvn --batch-mode -DskipTests package 

接下来,您运行测试,即使有失败的测试,也允许命令通过。这允许您将对失败测试的响应推迟到测试处理操作:

 - name: Test
        run: mvn --batch-mode -Dmaven.test.failure.ignore=true test 

在最后一步中,您将从 JUnit XML 文件生成一个报告。

if属性被设置为总是运行这个步骤,即使上面的Test步骤被设置为在测试失败的情况下失败,也允许您生成报告。

如果存在失败的测试,则将fail-on-error属性设置为true以使该工作流失败。这是一个将对失败测试的响应推迟到测试处理操作的示例:

 - name: Report
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Maven Tests
          path: target/surefire-reports/*.xml
          reporter: java-junit
          fail-on-error: true 

测试结果显示为原始工作流结果下的链接:

Java Tests Results

失败的测试显示其他详细信息,如测试名称、测试结果和原始测试输出:

Failed test

DotNET 中的单元测试

下面显示的工作流文件使用 DotNET Core CLI 运行测试,并使用 test-reporter 操作处理结果:

name: .NET Core

on:
  push:
  workflow_dispatch:

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout  
      uses: actions/checkout@v1

    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.402

    - name: Build
      run: dotnet build --configuration Release

    - name: Test
      run: dotnet test --logger "trx;LogFileName=test-results.trx" || true

    - name: Test Report
      uses: dorny/test-reporter@v1
      if: always()
      with:
        name: DotNET Tests
        path: "**/test-results.trx"                            
        reporter: dotnet-trx
        fail-on-error: true 

测试由 DotNET 核心 CLI 执行,并将结果保存为 Visual Studio 测试结果(TRX)报告文件。

如果任何测试失败,test命令会返回一个非零的退出代码,但是您将响应失败测试的责任委托给了测试处理器。通过将|| true链接到命令,您可以确保该步骤总是通过:

 - name: Test
      run: dotnet test --logger "trx;LogFileName=test-results.trx" || true 

然后,测试报告器动作处理报告文件,如果有任何失败的测试,将fail-on-error设置为true以使构建失败:

 - name: Test Report
      uses: dorny/test-reporter@v1
      if: always()
      with:
        name: DotNET Tests
        path: "**/test-results.trx"                            
        reporter: dotnet-trx
        fail-on-error: true 

DotNET Core Test Results

结论

GitHub Actions 主要是一个任务执行环境,旨在验证和构建代码,并发布结果工件。有许多第三方动作可以让你生成测试报告并对失败的测试做出响应,但是 GitHub 动作在跟踪测试结果方面有一些不足。尽管如此,今天可用的报告功能是有用的,而且只会改进。

在这篇文章中,你学到了:

  • 评估第三方处理测试结果的行动时要问的一些问题
  • 如何编写测试 Java 和 DotNET 核心应用程序的基本工作流
  • 如何处理测试结果并显示生成的报告

查看我们下一篇关于在 GitHub Actions 中测试的文章:

还可以了解一下为什么 GitHub 和 Octopus 在一起更好

愉快的部署!

GitHub 行动中的秘密-章鱼部署

原文:https://octopus.com/blog/githubactions-secrets

简而言之,“秘密”是加密的隐藏数据,但在您的项目中仍然可用。秘密可能是关于您的业务或验证方法的敏感数据,例如用于连接管道中其他服务的 API 密钥。

GitHub 允许你在 3 个不同的级别存储秘密:

  • 贮藏室ˌ仓库
  • 环境
  • 组织

在这篇文章中,我们来看看这三个层次,以及如何给它们添加秘密。我们还将看看如何在示例工作流中调用秘密。

仓库机密

GitHub 只将存储库秘密绑定到一个存储库。任何具有协作者角色的人都可以在操作中使用它们。

每个存储库可以存储 100 个秘密。

添加存储库密码

  1. 在 GitHub 中打开你的项目库,点击顶部菜单中的设置
  2. 点击左侧菜单中的秘密
  3. 点击新建存储库密码
  4. 填写以下字段并点击添加密码:
    • 给你的秘密取一个合适的名字。除下划线外,不能使用空格或特殊字符。
    • ——输入秘密,比如你的 API 密匙。

您可以随时更新您的秘密值。回到储存库菜单中的秘密,点击你需要更改的秘密上的更新

环境秘密

与 Octopus 一样,您可以将 GitHub 设置为使用环境部署到管道的部署目标。

如果您的回购是公开的,或者您拥有企业许可证,则可以设置仅适用于该环境的特定于环境的秘密。

每个环境中可以存储 100 个秘密。

创建新环境时添加秘密

  1. 在 GitHub 中打开你的项目库,点击顶部菜单中的设置
  2. 点击左侧菜单中的环境
  3. 点击新环境
  4. 为您的环境输入一个名称,然后点击配置环境
  5. 根据需要设置环境的保护规则和部署分支,然后点击 Add secret
  6. 填写以下字段并点击添加密码:
    • 给你的秘密取一个合适的名字。除下划线外,不能使用空格或特殊字符。
    • ——输入秘密,比如你的 API 密匙。

将机密添加到现有环境中

  1. 在 GitHub 中打开你的项目库,点击顶部菜单中的设置
  2. 点击左侧菜单中的环境
  3. 选择需要设置密码的环境。
  4. 点击页面底部的添加密码
  5. 填写以下字段并点击添加密码:
    • 给你的秘密取一个合适的名字。除下划线外,不能使用空格或特殊字符。
    • ——输入秘密,比如你的 API 密匙。

组织机密

  1. 在 GitHub 中打开您组织的页面,点击顶部菜单中的设置
  2. 点击左侧菜单中的秘密
  3. 点击新建组织机密
  4. 填写以下字段并点击添加密码:
    • 名字——给你的秘密取一个合适的名字。除下划线外,不能使用空格或特殊字符。
    • -输入秘密,例如您的 API 密钥。
    • 存储库访问 -从下拉列表中选择一个策略。您可以限制使用公共或私人回购,或手动选择它们。

在 GitHub 动作工作流程中使用机密

您不能将机密用于分叉存储库中的工作流。

不要在日志文件中捕获秘密。如果包含,GitHub 会隐藏秘密,但完全不包含秘密更安全。

您使用secrets上下文在工作流中调用您的秘密数据。GitHub 使用上下文从各种来源提取信息,包括工作流、作业和步骤。有关上下文的完整列表,请参见 GitHub 的文档。

例如,如果您想将一个包发送到 Octopus 服务器,作为工作流的一部分进行部署,那么您可以使用 secrets 上下文以及 secrets 的名称将您的工作流连接到 Octopus。

- name: Push Package to Octopus
   uses: OctopusDeploy/push-package-action@v1.0.0
   with:
       api_key: ${{ secrets.OCTOPUS_TEST_API }}
       packages: ${{ steps.package.outputs.artifacts }}
       server: ${{ secrets.OCTOPUS_PACKAGE_STORE }} 

为了解释发生了什么,工作流程的这一部分:

  1. 触发一个动作将包推给 Octopus。
  2. 从您的 secrets store 获取 Octopus API 密钥,以允许 GitHub 向 Octopus 传递数据。
  3. 根据您的操作步骤打包您的工件。
  4. 从您的机密存储中获取隐藏的包目标。

在这里,工作流使用 API 和目的地,但是将它们隐藏起来。

结论

机密是一种很好的安全措施,它允许您保护数据和连接到服务,而不会泄露敏感信息。

下一步是什么?

我们建议您查看 GitHub 的文档:

如果你还没有看过,可以看看这个持续集成系列中的其他一些博客。

Octopus 还构建了一个有用的 GitHub Actions 工作流生成器来帮助你为 GitHub 构建 CI 管道。

试用我们免费的 GitHub Actions 工作流工具,帮助您快速为 GitHub Actions 部署生成可定制的工作流。

观看我们的 GitHub 行动网络研讨会

https://www.youtube.com/embed/gLkAs_Cy5t4

VIDEO

阅读我们的持续集成系列的其余部分。

愉快的部署!

我们最喜欢的 10 个 GitHub 动作——Octopus Deploy

原文:https://octopus.com/blog/githubactions-ten-favorite-actions

尽管对持续集成(CI)世界来说相对较新,GitHub 的“动作”的加入已经见证了其强大的社区构建了直接插入到您的存储库中的有用任务。

操作允许您运行非标准任务,以帮助您测试、构建和将您的工作推送到您的部署工具。

排名不分先后,以下是我们的 10 个最爱,外加如何安装它们的。

1:测试记者

在 GitHub 中显示所有的测试结果, test reporter 动作有助于将代码和测试过程的重要部分放在一个地方。作为“检查运行”的一部分,提供 XML 或 JSON 格式的结果,这个动作用有用的统计数据告诉你代码在哪里失败了。

Test reporter 已经支持大多数流行的测试工具,例如。NET、JavaScript 等等。此外,你可以通过提出问题或贡献自己来增加更多内容。

支持的框架:

  • 。NET: xUnit、NUnit 和 MSTest
  • 飞镖:测试
  • 颤振:测试
  • Java: JUnit
  • JavaScript: JEST 和 Mocha

2:构建和推送 docker 映像

顾名思义,构建和推送 Docker 图像动作允许您构建和推送 Docker 图像。

使用 Buildx莫比 BuildKit 特性,你可以创建多平台构建、测试映像、定制输入和输出等等。

查看动作页面的完整功能列表,包括高级使用和如何定制它

3:设置 PHP

Setup PHP 动作允许你设置 PHP 扩展和。ini 文件,用于所有主要操作系统上的应用程序测试。

它还兼容 GitHub 的 composer、PHP-config、symfony 等工具。请访问市场页面,查看兼容工具的完整列表。

GitTools 动作允许你在管道中同时使用 GitVersionGitReleaseManager

GitVersion 通过语义版本化(也称为“Semver”)帮助解决常见的版本化问题,以实现项目间的一致性。GitVersion 有助于避免重复,节省重建时间,等等。有益于 CI 的是,它创建了标记构建的版本号,并使变量对管道的其余部分可用。

同时,GitReleaseManager 自动创建、附加和发布可导出的发行说明。

如果你只需要 GitVersion 的版本控制,这个列表后面还有一个同名的的替代动作。

5:动作自动释放

一旦设置为对您选择的 GitHub 事件(比如提交到您的主分支)做出反应,动作自动发布工作流可以:

  • 自动上传资产
  • 创建变更日志
  • 创建新版本
  • 将项目设置为“预发布”

6:存储库调度

存储库分派动作使得从‘存储库分派’事件触发动作变得更加容易。此外,它允许您触发和链接来自一个或多个存储库的操作。

你需要创建一个个人访问令牌来执行这个操作,因为默认情况下 GitHub 不支持它。

7:拉动预览

PullPreview 动作允许您通过构建代码评审的实时环境来预览拉请求。

当使用“pullpreview”标签进行拉取时,此操作会检出您的代码,并使用 Docker 和 Docker Compose 部署到 AWS Lightsail 实例。

这允许你像你的客户一样玩你的新功能,或者炫耀你的想法的工作模型。

它还承诺与您现有的工具兼容,并与 GitHub 完全集成。

你应该知道的唯一一件事是,如果将它用于商业产品,你需要购买许可证。

8:报告生成器

ReportGenerator 动作可以将覆盖率报告中最有用的部分提取成更容易阅读的格式。它允许您读取 HTML、XML 等格式的数据,以及各种文本摘要和特定于语言的格式。

9: Git 版本

虽然有点像 GitTools 动作启用的 GitVersion 工具 ,但是这个 Git version 本身就是一个动作。

然而,像外部工具一样,它提供了简单的 Semver 版本控制来帮助跟踪您的不同版本。如果您只想获得版本控制方面的帮助,而不需要 GitReleaseManager,这将非常有用。

10: GitHub 动作测试仪(github-action-tester)

github-action-tester 是一个让你启动 shell 脚本进行测试的动作。

安装完成后,只需将您的脚本添加到您的存储库中,然后用您需要的事件启动它们。

奖励回合:八达通

作为 GitHub 的官方技术合作伙伴,Octopus Deploy 已经在 GitHub 市场中了 10 个经过验证的 Octopus 操作,使您的部署过程自动化、执行任务、创建包等等变得更加容易。

鉴于 GitHub 动作服务的本质,其他用户也贡献了一些与章鱼相关的动作。如果你想与 Octopus 进行更多的集成,可以看看这些。您还可以了解为什么我们推荐您使用 GitHub 构建,使用 Octopus 部署。

如何安装操作

在 GitHub 中安装动作很简单:

  1. GitHub Marketplace 上找到你想要的动作。
  2. 阅读市场页面以检查先决条件。
  3. 点击右上角的使用最新版本(或者根据需要选择旧版本)。
  4. 从弹出窗口中复制代码,粘贴到您的存储库中。yml 文件,并保存。
  5. 请务必阅读该操作的文稿,以检查任何额外的设置以及如何使用该操作。

接下来呢?

如果我们选择的行动不适合您的项目,或者您需要 CI 范围之外的东西,还有很多可供选择。通过 GitHub 市场搜索更多信息。

试用我们免费的 GitHub Actions 工作流工具来帮助您快速为您的 GitHub Actions 部署生成可定制的工作流。

观看我们的 GitHub 行动网络研讨会

https://www.youtube.com/embed/gLkAs_Cy5t4

VIDEO

阅读我们的持续集成系列的其余部分。

愉快的部署!

使用带有 Octopus Deploy 的 GitLab 提要

原文:https://octopus.com/blog/gitlab-external-feeds

Octopus Deploy 有一个内置存储库,支持多种包类型。然而,一些客户更喜欢使用第三方存储库,或者使用内置于他们的持续集成(CI)工具中的存储库。

在这篇文章中,我演示了如何使用 GitLab 的内置注册表作为外部提要来使用 Octopus 部署您的项目。

GitLab 注册表

GitLab 支持多种注册表类型:

  • 包注册表
  • 集装箱登记处
  • 基础设施注册

在本文中,我将介绍包和容器注册。

包注册表

GitLab 包注册表支持多种包类型,但是,Octopus 只支持以下 GitLab 包注册表类型作为外部提要:

专家

Maven 存储库通常与 Java 应用程序相关联,尽管它们可以更通用地使用(稍后将详细介绍)。我们的 PetClinic 示例应用程序包含了演示 GitLab 的 Maven 注册功能所需的大部分组件。唯一缺少的是向 Maven 注册中心认证的机制。 GitLab 的文档向您展示了如何通过添加一个包含以下内容的ci_settings.xml文件来轻松实现这一点:

<settings  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
  <servers>
    <server>
      <id>gitlab-maven</id>
      <configuration>
        <httpHeaders>
          <property>
            <name>Job-Token</name>
            <value>${env.CI_JOB_TOKEN}</value>
          </property>
        </httpHeaders>
      </configuration>
    </server>
  </servers>
</settings> 

添加这些内容之后,您就可以定义您的构建定义了。

GitLab 使用基于 YAML 的方法来配置构建定义。下面是 PetClinic 应用程序的示例构建定义:

stages:
    - set-version
    - java-build
    - maven-push

set-version:
  stage: set-version
  tags:
    - shell
  script:
    - dayOfYear=$(date +%j)
    - hour=$(date +%H)
    - minutes=$(date +%M)
    - seconds=$(date +%S)
    - year=$(date +%y)
    - echo "1.0.$year$dayOfYear.$hour$minutes$seconds" >> version.txt
  artifacts:
    paths:
      [ version.txt ]

java-build:
  stage: java-build
  tags:
    - shell
  script:
    - VERSION_NUMBER=$(cat version.txt)
    - mvn clean package -DskipTests=true -Dproject.versionNumber=$VERSION_NUMBER
  artifacts:
    paths:
      - "$CI_PROJECT_DIR/target/*.war"

maven-push:
  stage: maven-push
  tags:
    - shell
  variables:
    GROUP: "com.octopus"
    VERSION: $VERSION_NUMBER
    REPO_PROJECT_ID: $CI_PROJECT_ID
  script:
    - VERSION_NUMBER=$(cat version.txt)
    - mvn deploy:deploy-file -s ci_settings.xml -DgroupId=$GROUP 
      -Dversion=$VERSION_NUMBER
      -Dfile=$CI_PROJECT_DIR/target/petclinic.web.$VERSION_NUMBER.war
      -Durl=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/maven
      -DrepositoryId=gitlab-maven
      -DpomFile=pom.xml
    - zip -r $CI_PROJECT_DIR/petclinic.mysql.flyway.$VERSION_NUMBER.zip $CI_PROJECT_DIR/flyway
    - mvn deploy:deploy-file -s ci_settings.xml
      -DgroupId=$GROUP
      -DartifactId=petclinic.mysql.flyway
      -Dpackaging=zip
      -Dfile=$CI_PROJECT_DIR/petclinic.mysql.flyway.$VERSION_NUMBER.zip
      -DrepositoryId=gitlab-maven
      -Durl=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/maven
      -DartifactId=petclinic.mysql.flyway
      -Dversion=$VERSION_NUMBER 

PetClinic 应用程序由两个主要组件组成:

  • Java web 应用程序
  • Flyway ,用于执行数据库更新

要使用 Octopus 部署这两者,首先需要将它们添加到 Maven 注册表中,这样 Octopus 就可以找到并部署它们。

构建定义的maven-push部分是工件上传到 Maven 注册中心的地方。注意,脚本部分定义了 2 个mvn deploy:deploy-file命令。第一个deploy命令将在java-build阶段编译的.war文件上传到注册表。

与 web 组件不同,Flyway 已经编译好了,你只需要压缩然后上传到注册表。zip命令压缩flyway文件夹的内容。

接下来,使用马特·卡斯珀森在上发布的关于从 Maven 部署和使用 ZIP 文件的指导,将.zip文件上传到 Maven 注册表。

package registry showing 2 packages

努格特

因为。NET 应用程序中,最常用的注册表类型是 NuGet。的。NET core 示例应用程序 OctoPetShop 很适合这个演示。

使用以下构建定义(该定义利用了 GitLab 的 Docker-in-Docker (DIND)特性):

image: ubuntu:latest

stages:
    - set-version
    - build-dotnet
    - package-dotnet
    - push-packages

set-version:
  stage: set-version
  script:
    - dayOfYear=$(date +%j)
    - hour=$(date +%H)
    - minutes=$(date +%M)
    - seconds=$(date +%S)
    - year=$(date +%y)
    - echo "1.0.$year$dayOfYear.$hour$minutes$seconds" >> version.txt
  artifacts:
    paths:
      [ version.txt ]

build-dotnet:
  stage: build-dotnet
  image: mcr.microsoft.com/dotnet/core/sdk:3.1
  script:
    - VERSION_NUMBER=$(cat version.txt)
    - dotnet build OctopusSamples.OctoPetShop.Database/OctopusSamples.OctoPetShop.Database.csproj --output "$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.Database"
    - dotnet publish OctopusSamples.OctoPetShop.ProductService/OctopusSamples.OctoPetShop.ProductService.csproj --output "$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.ProductService"
    - dotnet publish OctopusSamples.OctoPetShop.ShoppingCartService/OctopusSamples.OctoPetShop.ShoppingCartService.csproj --output "$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.ShoppingCartService"
    - dotnet publish OctopusSamples.OctoPetShop.Web/OctopusSamples.OctoPetShop.Web.csproj --output "$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.Web"

  artifacts:
    paths:
      - "$CI_PROJECT_DIR/output/"

package-dotnet:
  stage: package-dotnet
  image: octopuslabs/gitlab-octocli
  script: 
    - VERSION_NUMBER=$(cat version.txt)
    - octo pack --id=OctopusSamples.OctoPetShop.ProductService --version=$VERSION_NUMBER --basePath="$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.ProductService" --outFolder="$CI_PROJECT_DIR/packages" --format="NuPkg"
    - octo pack --id=OctopusSamples.OctoPetShop.Database --version=$VERSION_NUMBER --basePath="$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.Database" --outFolder="$CI_PROJECT_DIR/packages" --format="NuPkg"
    - octo pack --id=OctopusSamples.OctoPetShop.ShoppingCartService --version=$VERSION_NUMBER --basePath="$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.ShoppingCartService" --outFolder="$CI_PROJECT_DIR/packages" --format="NuPkg"
    - octo pack --id=OctopusSamples.OctoPetShop.Web --version=$VERSION_NUMBER --basePath="$CI_PROJECT_DIR/output/OctopusSamples.OctoPetShop.Web" --outFolder="$CI_PROJECT_DIR/packages" --format="NuPkg"
  artifacts:
    paths:
      - "$CI_PROJECT_DIR/packages/"

push-packages:
  stage: push-packages
  image: mcr.microsoft.com/dotnet/core/sdk:3.1
  script:
    - dotnet nuget add source "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text
    - dotnet nuget push "$CI_PROJECT_DIR/packages/*.nupkg" --source gitlab 

默认情况下,web 应用程序的IsPackable属性设置为 false。您可以使用 Octopus Deploy GitLab CLI 映像将编译后的应用程序打包到 NuGet 包中,而不是弄乱项目属性。在创建了.nupkg文件之后,可以使用dotnet命令将包推入 GitLab NuGet 注册表。

集装箱登记处

除了包注册表,GitLab 还有一个容器注册表。为了演示如何使用容器注册中心,您可以再次使用 OctoPetShop 应用程序:

image: ubuntu:latest

stages:
    - set-version
    - build-docker

set-version:
  stage: set-version
  script:
    - dayOfYear=$(date +%j)
    - hour=$(date +%H)
    - minutes=$(date +%M)
    - seconds=$(date +%S)
    - year=$(date +%y)
    - echo "1.0.$year$dayOfYear.$hour$minutes$seconds" >> version.txt
  artifacts:
    paths:
      [ version.txt ]

build-docker:
    stage: build-docker
    image: docker:1.11
    services:
      - docker:dind
    before_script:
      - docker info
    script:
      - VERSION_NUMBER=$(cat version.txt)
      - docker build -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.database:$VERSION_NUMBER ./OctopusSamples.OctoPetShop.Database/ 
      - docker build -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.productservice:$VERSION_NUMBER ./OctopusSamples.OctoPetShop.ProductService/ 
      - docker build -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.shoppingcartservice:$VERSION_NUMBER ./OctopusSamples.OctoPetShop.ShoppingCartService/ 
      - docker build -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.web:$VERSION_NUMBER ./OctopusSamples.OctoPetShop.Web/ 
      - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
      - docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.database:$VERSION_NUMBER
      - docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.productservice:$VERSION_NUMBER
      - docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.shoppingcartservice:$VERSION_NUMBER
      - docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/octopetshop.web:$VERSION_NUMBER 

将 GitLab 注册表作为外部提要进行连接

除了内置的存储库之外,Octopus Deploy 还支持使用外部提要来获取用于部署的包。

要添加外部提要,请转到您的 Octopus 仪表盘,点击,然后点击外部提要,再点击添加提要

Octopus dashboard on Library tab with External Feeds and ADD FEED highlighted in the UI.

添加 GitLab Maven 提要

要添加 GitLab Maven 注册表,您需要与提要相关联的项目或组 ID。这篇文章展示了项目的水平。要获取项目 ID,请在 GitLab 中导航到该项目,ID 会显示在初始屏幕上:

填写 Octopus 中的表单字段:

  • 进给类型 : Maven Feed
  • Name :给 feed 起一个名字,比如GitLab Petclinic Maven Feed
  • 网址 : https://[gitlabserver]/api/v4/projects/[project or group id]/packages/maven
  • (可选)凭证:访问提要的用户名和密码

Octopus dashboard showing Create Feeds screen under External Feeds section

点击保存并测试以确保进给功能正常。使用“组:工件”格式输入包的名称。

添加 GitLab NuGet 提要

与 Maven 提要类似,您需要与提要相关联的项目或组 ID。

填写表单字段:

  • 进给类型 : NuGet Feed
  • 名称:给提要起一个名字,例如GitLab OctoPetShop Nuget Feed
  • 网址 : https://[gitlabserver]/api/v4/projects/[project or group id]/packages/nuget/index.json
  • (可选)凭证:访问提要的用户名和密码

Octopus dashboard showing Create Feeds screen under External Feeds section

点击保存并测试以确保进给功能正常。输入包的名称(可以是部分名称):

Octopus External Feed

添加 GitLab 容器提要

与其他两种提要类型不同,容器提要不需要项目或组 ID。但是,它确实需要知道服务器上的哪个端口被配置为托管容器注册中心。

填写表单字段:

  • 进给类型 : Docker Container Registry
  • 名称:给 feed 起一个名字,比如GitLab Container Registry
  • 网址 : https://[gitlabserver]:[port]
  • 凭证:访问提要的用户名和密码

Octopus dashboard showing Create Feeds screen under External Feeds section

点击保存并测试以确保进给功能正常。输入包的名称(可以是部分名称):

Octopus External Feed

结论

在许多情况下,使用 Octopus Deploy 的内置存储库会让您受益匪浅。但是,如果你需要跨空间的东西,又不想重复,也有替代方案。在这篇文章中,我向您展示了如何使用 GitLab 的内置注册表作为外部提要来使用 Octopus 部署您的项目。

愉快的部署!

posted @ 2024-11-01 16:33  绝不原创的飞龙  阅读(14)  评论(0编辑  收藏  举报