ようへいの日々精進XP

よかろうもん

2019 年 06 月 09 日 (日)

ジョギング

山王公園を 30 分くらい, 懸垂 x 6 回

ランチ

近所のラーメン屋さん. 博多ラーメン. スープが美味しかった.

ダラダラ

ランチの後, 出川哲朗が充電して回る番組を見ながら昼寝してしまった...その後はダラダラと過ごしてしまった.

夕飯

久しぶりに頑張ってパスタを作った. オイルが多すぎて奥さんには不評だったので, 残飯処理的に自分でほとんど食べた.

2019 年 06 月 08 日 (土)

ジョギング

博多 -> 天神 -> 博多をグルっと 1 時間くらい. 山王公園にゴールして 約 400m x 1, 懸垂 x 6 回

G20

のおかげで, 市街地付近は警察官が多く立っていた.

博多デート

奥さんと博多をぶらり. 夕飯は串カツ. 久しぶりの串匠. 美味しかった.

最近ギョームでやったこと (2) 〜 もう 2019 年なのに, 今更だけど EC2 運用の ChatOps にチャレンジしているメモ 〜

tl;dr

タイトルの通りです.

ChatOps という言葉がインターネット上に流布されるようになったのは, 古くは 2003 年!!, そしてグーッと盛り上がってきたのが 2013 年!!ということなので, 今更感がありますが YAMAP にて EC2 の運用に ChatOps を取り入れようとしているので, どんなことをやっているのかをメモっておきます.

YAMAP 社内では EC2 インスタンスでいくつか社内サービスを提供していますが, その中でも, エンジニアチームが検証の為に自由に利用する環境が複数台 EC2 で稼働しています. 今回はこの検証環境用 EC2 (OS は Amazon Linux) を ChatOps で運用するようにしてみた話です.

課題

検証環境用 EC2 には以下のような課題がありました & あります. (現在進行系の課題もあります)

  1. 常時起動する必要は無いので, 会社の始業と共に起動して, 終業と共に停止するようにしたい (自動起動, 自動停止)
  2. 誰が使っているのかをシュッと把握したい (利用者の把握)
  3. サーバーにログインしていなくても, サービスの稼働状態を確認出来るようにしたい
  4. サーバーにログインしていなくても, サービスの起動や停止を制御出来るようにしたい
  5. 必要に応じて, 利用したい人が新しい環境作ることが出来る

そして, 検証したい人が「検証したいだけなのに, サーバーのことを気にするのが辛い」というようなハードルを下げたいのと, 何より, 「俺が楽したい」という思いがあります.

ということで, これらの課題をどのように解決したのか, 解決しようとしているのかをざっくりとで恐縮ですがダンプしていきたいと思います.

施策

概要図

以下のような感じでやっています.

f:id:inokara:20190608152604p:plain

YAMAP における bot 文化

YAMAP は業務に Slack を利用しており, 業務インフラには無くてはならない存在になっています. そして, Slack には Bot (Hubot) が飼われていて, 社内の福利厚生であるシャッフルランチや掃除当番の抽選から Github と連携し CircleCI によるテストやデプロイの指示役等大活躍しております. この bot を仕込んで, 社内文化の一つとして作り上げてくれたのは @morygonzalez さんには本当に感謝です. ちなみに bot の名前は tengu 氏と呼びます.

portalshit.net

ということで, 既に bot 文化は根付いていることから, 少なくとも, エンジニアチームのメンバーは bot を操作することに対するアレルギーは無いという前提で色々と進めたいなーと考えています. 気軽に bot を呼び出して欲しいなあと.

自動起動, 自動停止

これは YAMAP にジョインした時点で既に自動起動, 自動停止の仕組みは構築されていました.

まず, 以下のように tengu 氏に呼びかけると, 指定した EC2 インスタンスが起動します. 停止も同様です. 当然, 以下のコマンドを叩くことで, 任意のタイミングでの起動や停止が可能になっています.

# tag:Name kensho01 を起動する
tengu ec2 start kensho01

# tag:Name kensho01 を停止する
tengu ec2 stop kensho01

自動起動や自動停止は, このコマンドを別の bot (cronbot 氏) に叩かせています. この cronbot 氏は, Slack の slash コマンド経由で呼び出すことで, 通常の crontab と同じ感覚で利用することが可能です.

# crontab -l と同等
/cron list

# 月曜日から金曜日までの 9 時に tengu ec2 start kensho01 を実行
/cron add 0 9 * * 1-5 tengu ec2 start kensho01

cronbot 氏は, 以下の OSS を利用させて頂いています.

github.com

ありがとうございます.

自動起動, 自動停止だけであれば, AWS Lambda を利用するという選択肢がありますが, 任意のタイミングでの起動や停止, 自動起動, 停止の時間をカジュアルに変更し辛いので, Slack のコマンドでちゃちゃっと出来る点は素晴らしいなあと思っています.

利用者の把握

前述の通り, 検証環境用 EC2 はエンジニアであれば誰 (エンジニアに限ってはいませんが) でも利用が可能です. ただ, 誰が利用しているかは, Slack のエンジニアチームチャンネルに問い合わせる必要がありました.

@here kensho01 を使いたいんですが, どなたか使っていますかー?

これでは非効率なので, EC2 のタグに利用者を登録する仕組みを追加しました. まずは, 誰 ( or どのチーム) が利用しているのかを確認する為に, 以下のように tengu 氏に呼びかけます.

tengu feature reservations?

下図のように出力されます.

f:id:inokara:20190608101527p:plain

Reserved By が空欄になっている為, 誰にも利用されていないということにしています. ここで, インスタンス kensho01 を利用したい場合には, 以下のように呼びかけます.

tengu kensho reserve 01 kappa

下図のように出力されます.

f:id:inokara:20190608102129p:plain

利用状態を解除したい場合には, 以下のように呼びかけます.

tengu kensho release 01

これらの仕組みは, 先述の通り, EC2 タグを利用していて, reserve を呼ぶと EC2 タグ reserved_by に第四引数 (上記の例だと kappa) を登録しているだけです. 同様に release を呼ぶと EC2 タグ reserved_by の値をクリアしています.

サービスの起動, 停止, 稼働状況の確認

AWS Systems Manager

ここから少し込み入った話になります.

検証環境用 EC2 上では素の Docker エンジンが動いていて, 各種サービスは docker-compose で管理されたコンテナで起動しています. これらのコンテナが起動しているか, また, コンテナ上のサービスが利用可能であるかインスタンスにログインせずに確認出来れば嬉しいのではと考えました. また, 確認だけでなく, コンテナの起動や停止も行えれば, 更に利便性も上げられそうです.

ということで, これらの要件を AWS Systems Manager を利用して実装してみました.

docs.aws.amazon.com

AWS Systems Manager とは, EC2 インスタンスだけではなく, オンプレミスサーバーや他の AWS リソースの設定を管理する機能を集めたもので, その中から AWS Systems Manager Run Command (以後, Run Command) を利用しました.

docs.aws.amazon.com

Run Command は簡単に言うと, EC2 やオンプレミスサーバーにインストールされている SSM エージェントを介して, ドキュメントと呼ばれる, 予め用意された (用意している) コマンドを実行してくれるサービスです.

Run Command (1)

検証環境用 EC2 インスタンスには, docker-compose up -d やコンテナ上で稼働するサービスの動作を確認する為のコマンドをまとめたスクリプトを用意しておきます. EC2 インスタンス再起動時に実行出来るように, init スクリプトとして用意しました.

#!/bin/sh
# chkconfig: 2345 99 10
# description: control containers for kensho environment

USERNAME=xxxxxx

function start_containers() {
  /sbin/runuser -l ${USERNAME} -s /bin/bash -c "
    eval $(aws ecr get-login --no-include-email --region ap-northeast-1)
    docker-compose pull
    docker-compose stop
    docker-compose up -d
  "
}

function get_kensho_service_status() {

... 略 ...

}

case "$1" in
  start)
    start_containers
    ;;
  stop)
    stop_containers
    stop_ssmweb
    ;;
  status)
    get_kensho_service_status
    ;;
  *) break ;;
esac

EC2 インスタンス上では, 以下のように実行されることを想定しています.

$ sudo service kensho-service status
===== Certificate Status
notBefore=Jun  4 02:46:05 2019 GMT
notAfter=Sep  2 02:46:05 2019 GMT

===== Containers Status
NAMES               CREATED             STATUS              IMAGE
container1       4 minutes ago       Up 4 minutes      image1
container2       4 minutes ago       Up 4 minutes     image2
conteiner3       4 minutes ago       Up 4 minutes     image3

===== API Status
HTTP/1.1 200 OK

===== Web Status
HTTP/1.1 200 OK

===== Batch Status
HTTP/1.1 200 OK

start では, コンテナの起動 docker-compose -d や, サービスを動かす為の各種初期設定が実行されます. stop では, 単純に docker-compose down が実行されます.

Run Command (2)

上記の init スクリプトを Run Command で実行します. 以下のようなシンプルなドキュメントを用意しました.

{
  "parameters": {
    "action": {
      "type": "String",
      "description": "(Required) Specify a action(start|stop|status).",
      "displayType": "textarea"
    }
  },
  "schemaVersion": "2.2",
  "description": "update kensho environment",
  "mainSteps": [
    {
      "action": "aws:runShellScript",
      "name": "control_kensho_env",
      "inputs": {
        "runCommand": [
          "service kensho-env {{ action }}"
        ]
      }
    }
  ]
}

あとは, hubot から Run Command でコマンドを実行する為の API を叩くだけ (厳密に言うと, IAM ポリシーを付与したりする必要はあります) です. API を叩く部分の実装は以下のような感じです.

// 略 ....
  const sendCommand = async (name, action) => {
    const targetInstance = await detectInstance(name)
    let result
    try {
      result = await ssm.sendCommand({ DocumentName: 'control_kensho_env', InstanceIds: [targetInstance.InstanceId], Parameters: { 'action': [ action ]} }).promise()
    } catch(e) {
      result = e
    }
    return result
  }

  const checkIfOperationFinished = async (name, commandId, channel) => {
    const targetInstance = await detectInstance(name);
    let message;
    let count = 0;
    while(true){
      result = await ssm.listCommandInvocations({ CommandId: commandId, InstanceId: targetInstance.InstanceId, Details: true }).promise()
      if (result.CommandInvocations.length != 0 || count > 20){
          if (result.CommandInvocations[0].StatusDetails != 'InProgress') {
            break;
          }
      }
      count++;
      sleep.sleep(3);
    }
    message = result.CommandInvocations[0].CommandPlugins[0].Output;
    robot.messageRoom(channel, "コマンドの実行結果です.\n```\n" + message + "\n```")
  }
// 略 ....

見よう見まねの実装なので, すごく汚いかもしれませんが, sendCommand してから listCommandInvocations をループでコールしてコマンドの終了を待ったりする部分は苦労しました.

あとは

以下のように tengu 氏に問い合わせます.

# kensho01 のサービス稼働状況を取得します
tengu kensho service status 01

# kensho01 のサービスを停止します
tengu kensho service stop 01

# kensho01 のサービスを起動します
tengu kensho service start 01

以下, 実際に tengu kensho service status 01 を実行している様子です.

f:id:inokara:20190608144423p:plain

startstop もこんな感じの出力になります.

これから

EC2 の起動や停止, サービスの起動, 停止や稼働確認はやりたいことが出来た気がしています. 次のステップとして, 必要に応じて, 検証環境用 EC2 インスタンスを chatops で手軽に増やしていけるような仕組みを考えたいと思います.

これは EC2 じゃなくても良いかなと. ECS だとクラスタを増やしていけば良いのかな. ECS や EKS 等, 既にベンダからは様々なソリューションが提供されています. これらをうまく組み合わせて, よりエンジニアの皆さんの利便性が上がるように, そして自分が楽になれるような仕組みを考えていければなあと考えています.

まとまっていないまとめ

ということで, YAMAP に入社してから半年が経とうとしていますが, こんな感じのことを色々とやっていますので, 今後共よろしくお願いいたします.

冒頭に書きましたが, chatops という言葉が夜に放たれてから 10 年以上の時を経て, 今更感は否めませんが, 業務効率向上の為, そして, 何よりもインフラ担当者が少しでも楽になるような仕組みはドンドコ導入していきたいし, 手を動かして行ければ (手を動かさなくても回るのがベスト) なあと考えています.

2019 年 06 月 07 日 (金)

ジョギング

また, 寝坊してしまった...

大雨

昨晩からの大雨は明け方まで続いた. 4 時半くらいに起きたらベランダとかが大変なことになっていて, 奥さんと慌てて片付けをした.

ランチ

会社の近所の博多一番どりで白湯麺を食べた. スープが美味しかった.

ディナー

居酒屋ひろみ. 鮭のバター焼きが皮までパリパリで美味しかった.

2019 年 06 月 06 日 (木)

ジョギング

寝坊.

JAWS-UG 福岡もくもく会

青柳さんの近況話から, 今日, 初参加の方との話等, AWS の話をちゃんとたくさん出来た気がする. こういうのを積み重ねると良いような気がする.

夕飯

奥さん特製のハンバーグ. 今日は, 若干ゆるい目玉焼きをのっけて美味しかった!

2019 年 06 月 05 日 (水)

ジョギング

山王公園 35 分. 懸垂を 6 回. 引き続き, 左足かかと付近に違和感あり.

ランチ

奥さんにお弁当を作ってもらった. ポテトサラダサンドイッチ. とても美味しかった. ポテトサラダはもちろんだが, パンもしっとりしていて最高だった.

fukuoka.rb

YAMAP にて開催.

自分の進捗は芳しくなかったけど, mruby が動く M5Stack というマイコンの話で盛り上がって楽しかった.

www.switch-science.com

夕飯

奥さん特製のハンバーグ. 美味しかった!!!が, やっぱり, 出来たてよりもジューシー感がイマイチだった.

2019 年 06 月 04 日 (火)

ジョギング

山王公園 35 分. 懸垂を 6 回. 昨日よりは楽にはしることが出来たけど, 左足かかと付近に違和感あり.

仕事

いろいろと上手くいかない. 少し, 気分転換が必要だなと思ったり.

CloudFront + Nginx の組み合わせによる Content-Encoding: gzip について

知見を得た. ちょっとまとめたいと思う.

2019 年 06 月 03 日 (月)

ジョギング

山王公園 30 分くらい (時計を押すの忘れた). 懸垂を 6 回. めっちゃキツイし, 足がなぜかパンパン.

週明けから

バタバタしてた.

いろいろ学んだのでブログ書いた

inokara.hateblo.jp

文章書く力が無さすぎるので小学生の方がもっとまともなことを書くかもしれないけど.

最近, 身の回りに起きたことから, 改めて感じた証跡を残すということの大切さと他人の為に動くという難しさ

tl;dr

最近, 一番近い身近で起きていたことから, 改めて感じたこと等を稚拙な文章になってしまい恥ずかしい限りですが, ダラダラと書いていきます.

大事, 本当に大事

あくまでも個人的な感覚になってしまいますが, 仕事でもプライベートでも, 以下のような状況においては絶対に証跡は残すことが本当に大事. どんなことがあっても証跡を残すこと.

  • なんちゃら仲介業 (不動産とか人材派遣とか) との契約周りの話し
    • あったりまえの事を言っているかもしれないけど, ↑ のような会社では, なぜか知らないけど, そういう話でも平気で電話で掛けてくる
  • ↑このようなやりとりを電話だけで済まそうとする方々とは, その場で付き合いを止めても良い
  • 証跡は手書きメモでは不十分だし, 必ず相手と共有すべき
  • 電話の場合には, 改めてメールで送らせるか, 録音する (相手の同意がない録音って法的な拘束力が無いという情報もあるらしいけど...)

証跡が全く無い場合, あったとしても相互に共有されていない場合, 然るべき場所に駆け込んだところで, 「証拠がないとねえ」って言われて, 半ば門前払いになります. たとえ, 話を聴いてくれたとしても, 「これ以上楯突いてもお金と時間の無駄ですよ〜」と暗に諭されるはずです.

今回, 自分の身近で起きたのは, 当方には手書きレベルのメモがあり, それを元に代理の方が先方に確認したところ, その内容はでっち上げ扱いされた挙げ句に, 先方の記録によって代理の方が諭されてしまうという事態が発生して笑ってしまいました. わかってはいましたが, 証跡を残すことだけではなく, それは双方で共有すべき. これを改めて痛感しました.

誰かの為に動くという事

誰かの為に動くという事は悪いことではありません. 困っている人が居れば積極的に手を差し伸べるべきだと教えられてきました.

ただ, その差し伸べた手をいつまでも差し出し続けるのか, 適当な時期を見計らって手を引くのか, その判断はとても難しいことだと思います. だからこそ, 誰かに手を差し伸べる時には, そこを見据えて動くべき (そこまで責任を取るつもりで動くべき) だと思います. 想定しない圧力や, 状況の変化によって, 急に手を引くくらいであれば, 最初から手を差し伸べるべきではない (関わるべきではない) と思いますし, 自分自身も, 誰かにそういう態度を取ってしまっていないか猛省しました.

急に手を引かれるとどうなるか. 「梯子を外される」という言葉の意味とは違いますが, マンガ的にはまさに, 途中まで登った壁から急にはしごを外されて, 登り切るか, 飛び降りるかの選択肢しか無くなって困っている人が壁に張り付いたままどうしようもない状態が目に浮かびます.

今回, 当事者は壁を登りきったと思います. 梯子無しで登りきりました. ただ, 登りきった先には, さっきまで梯子を持っていてくれた人が何事も無かったように立っているだけした. なぜ, 梯子を外したのかは誰もわかりません.

以上

人間不信のその先に, 自分は何を思えば良いのかわからなくなりました.

最近ギョームでやったこと (1) 〜 EC2 のメモリ使用率とディスク使用率を監視するツールを作って, CircleCI で RPM パッケージを生成出来るようにした 〜

tl;dr

現在, 働いている会社 (YAMAP) では, EC2 インスタンス (OS は Amazon Linux がメイン) がまだ数台動いています. これらのインスタンスのメモリとディスクの使用率を CloudWatch のカスタムメトリクスに送りつけて監視を行いたかったのでツールを作りました. そして, そのツールを CircleCI でビルドして, さらに RPM パッケージ化出来るようにしたので, その概要と苦労した点をメモります.

尚, Datadog 等の SaaS ツールの方がええやんという思いはありましたが, これらの EC2 は退役を控えているシステムであり, あまりコストはかけず, お手軽に監視を追加する方法を考えた結果自作ツールとなりました. 技術的な負債としては小さいものだと考えています.

まだまだベータ版

まだまだベータ版なので, 以下のような課題を抱えています.

  • RPM バッケージをどこにアップロードするか検討中... 多分, S3 に置くけど, 安全に RPM リポジトリとして利用する方法を調査中
  • RPM パッケージのインストール手順の作成 (itamae とか Ansbile とか)

監視ツールについて

メモリ使用率, ディスク使用率を監視するツールは勉強を兼ねて, 先人達の知恵を借りつつ Go で実装してみました. コードについては, 近日中に追加したいと思いますが, 以下のような機能を提供しています.

  • メモリ監視は /proc/meminfo から情報を取得する
  • ディスクに関しては, syscall パッケージ を利用して情報を取得する
  • 取得した情報を計算して CloudWatch のカスタムメトリクスとして登録する

尚, 実装にあたり, 以下のリポジトリのコードを参考にさせて頂き, Go ルーチンを利用した実装にしてみました. とても参考になりましたありがとうございました.

github.com

メモ

メモリ使用率監視

イチからメモリ監視の実装を書くのは辛い (というか, 無理です) ので, 以下のパッケージを利用しました.

/proc/meminfo 以下の情報をパースして構造体として返してくれるので, 以下のように直感的にメモリの使用量等を取得出来ます.

       memory, err := memory.Get()
        if err != nil {
            fmt.Fprintf(os.Stderr, "%s\n", err)
            return
        }
        if *argDebug {
            fmt.Println("---------- debug print ----------")
            fmt.Printf("memory total: %d bytes\n", memory.Total)
            fmt.Printf("memory used: %d bytes\n", memory.Used)
            fmt.Printf("memory cached: %d bytes\n", memory.Cached)
            fmt.Printf("memory free: %d bytes\n", memory.Free)
        }

最高です.

ディスク使用率監視

先述の通り, syscall パッケージを利用しています. 以下のような短いコードで指定したパスの使用量等を取得しています.

func DiskUsage(path string) (disk DiskStatus) {
    fs := syscall.Statfs_t{}
    err := syscall.Statfs(path, &fs)
    if err != nil {
        return
    }
    disk.Total = fs.Blocks * uint64(fs.Bsize)
    disk.Free = fs.Bfree * uint64(fs.Bsize)
    disk.Used = disk.Total - disk.Free
    return
}

また, メモリ使用率と同じような感じで取得出来るようにしました.

       disk := DiskUsage(*argPath)
        if *argDebug {
            fmt.Println("---------- debug print ----------")
            fmt.Printf("disk total: %d bytes\n", disk.Total)
            fmt.Printf("disk used: %d bytes\n", disk.Used)
        }

RPM パッケージ

RPM パッケージを生成するにあたり, 以下のパッケージが必要になります.

yum install rpmdevtools yum-utils

また, 肝になるのは SPEC ディレクトリ以下に設置する以下のような spec ファイルです.

%define _binaries_in_noarch_packages_terminate_build 0

Summary: EC2 Monitoring Toolset for disk Usage
Name:    ec2-monitoring-toolset-disk
Version: 0.0.3
Release: 1
License: MIT
Group:   Applications/System
URL:     https://github.com/xxxxxxxx/ec2-monitor-tools

Source0:   %{name}
Source1:   %{name}.initd
Source2:   %{name}.logrotate
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root

Requires: logrotate

%description
%{summary}

%prep

%build

%install
%{__rm} -rf %{buildroot}
%{__install} -Dp -m0755 %{SOURCE0} %{buildroot}/usr/local/bin/%{name}
%{__install} -Dp -m0755 %{SOURCE1} %{buildroot}/%{_initrddir}/%{name}
%{__install} -Dp -m0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/logrotate.d/%{name}

%clean
%{__rm} -rf %{buildroot}

%post
/sbin/chkconfig --add %{name}
/sbin/chkconfig --level 3 %{name} on

%files
%defattr(-,root,root)
/usr/local/bin/%{name}
%{_initrddir}/%{name}
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}

個々の設定内容については言及しませんが, init スクリプト等の関連するスクリプトの設置までこのファイルでやれるということを知りました. 上記のファイルでは,

Source0:   %{name}
Source1:   %{name}.initd
Source2:   %{name}.logrotate
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root

... 略

%install
%{__rm} -rf %{buildroot}
%{__install} -Dp -m0755 %{SOURCE0} %{buildroot}/usr/local/bin/%{name}
%{__install} -Dp -m0755 %{SOURCE1} %{buildroot}/%{_initrddir}/%{name}
%{__install} -Dp -m0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/logrotate.d/%{name}

このあたりです. また, 配布したいツール自身 (今回だと, Go でビルドしたバイナリファイル) は SOURCES ディレクトリに放り込んでおけば良いことも知りました. ですので, Go でビルドしたバイナリではなくても, シェルスクリプト等も同じように RPM パッケージとして配布することが出来ます. 社内等の組織の中でバッチ処理等のスクリプトを配布する場合, 配布の手法を統一するという意味では RPM パッケージ化するというのもありかもしれません.

CircleCI

Workflows を使っています. 以下のようなシンプルなワークフローです.

f:id:inokara:20190602235724p:plain

  • build ジョブでは go test (取って付けたようなテスト) が行われた後, RPM パッケージを生成します
  • 生成された RPM パッケージを実際に Amazon Linux コンテナにインストールして, 意図した通りにインストールされているか等をテストするのが test ジョブです
  • deploy ジョブは RPM パッケージを S3 バケットにアップロードするステップを想定しています

今回の作業で得た知見は, 最初の build ジョブで生成された RPM パッケージを次の test ジョブや deploy ジョブに共有したい場合どうするか. .circleci/config.yml の以下の部分で指定しています.

jobs:
  build:
    docker:
      - image: amazonlinux:1
    working_directory: /root/go/src/toolset
    steps:
... 略 ...
      - persist_to_workspace:
          root: /root/rpmbuild/RPMS/x86_64
          paths:
            - ./*

  test:
    docker:
      - image: amazonlinux:1
    steps:
      - attach_workspace:
          at: /root/rpmbuild/RPMS/x86_64
      - checkout
... 略 ...

  deploy:
    docker:
      - image: amazonlinux:1
    steps:
      - attach_workspace:
          at: /root/rpmbuild/RPMS/x86_64
... 略 ...
 
workflows:
  build-test-deploy:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - test

build ジョブの stepspersist_to_workspace キーで共有したいパスを指定して, 次のジョブの stepsattach_workspace.at で共有されたパスを指定すれば良いようです. 詳細についは, 以下のドキュメントに記載されています.

以上

メモでした. ちゃんと RPM パッケージを作ったことが無かったのでとても勉強になりましたし, Go をちゃんと勉強しなければと思った次第です.