ブロックチェーンの DevOps について考えてみる (Ethereum での構築手順あり)

ブロックチェーンの DevOps について考えてみる (Ethereum での構築手順あり):

ブロックチェーンでの DevOps 実現について考えた後に、Ethereum の DevOps パイプラインを構築する方法を紹介します。今回は Azure DevOps を使ってパイプラインを組みますが、ローカルでの環境設定は Azure であっても、他のクラウドや CI/CD 環境でも変わりません。


前提:そもそも Blockchain で DevOps は可能なのか。

あまりこれは日本語の記事や投稿では多くは見かけない概念ですが、少なくとも前から方法は紹介されています。
https://truffleframework.com/tutorials/ethereum-devops-truffle-testrpc-vsts

ただし、前提としてブロックチェーンの環境で 従来の CI/CD プロセスおよび DevOps を当てはめるのは難しいと言われています。

大きな理由として挙げられてるのが、不変の物をのせるためのブロックチェーンプラットフォームなのに、そもそもコントラクトのロジックが変わってしまったら問題だということです。

管理されていない状況下で、コントラクトの変更が頻繁に起こると、そもそもブロックチェーンを使った意味ってなんだっけ?となりますよね。

特にパブリックチェーンの場合、それぞれのコントラクトの整合性や後方互換性がユーザーに与える影響が大きくなりますし、旧バージョンのコントラクトと新バージョンのコントラクトが変わったタイミングや内容に関しても全てトラック、管理されて公開されている必要がでてきます。

プライベートチェーンに関しても同じことが言えます。結局のところ開発においてブロックチェーンの接続部分が汚くなる傾向にあります。

例えば、開発に置いて JSON スキーマがどんどん変更されていき、v1、v2、v3 とどんどんプロパティが増えていくことを想像してみてください。そうした際にはおそらくみなさん、どこかしらのポイントでのフォーマットの統合をしたり、中間テーブルなどで対処されていると思います。

ブロックチェーンにおいてはその統合が非常に難しいと考えてください。

こうした事情から、従来の CI/CD のように、小さな改変を1週間のうちに何回も繰り返し、CM (コンテニュアスモニタリング)も取り入れて、バグの早期発見をして改変していく。また、カナリアリリースをしてエラーを最小限に抑える。とかは全く違う概念です。


今のコントラクト開発 はファミコンの開発である

今のブロックチェーンはファミコンだと考えると理解が早いです。


image.png


(引用:wikipedia)

ここでは、ファミコン = コントラクト、カセット = トランザクション と考えてみます。ちょっと乱暴かもしれませんがご容赦ください。

(「カセット = トランザクション?え?」とか「ファミコンがコントラクトなら、イーサリアム自体は?」とか厳しいことは言わないでください...)

ファミコンの特徴は以下です。

- ファミコンは一回市場に出てしまったら、ハードウェア自体を回収することができません。

- ファミコンのコンソールは変更できないので、市場に出る前に厳しいテストを経ている必要があります。

- ファミコンの使用(いわゆるコントラクト)にそってゲームカセット(トランザクション)は作られます。


バージョンが新しいコントラクトはスーパーファミコン

では、ファミコンに限界がきてバージョンアップしたくなったらどうなるでしょう?

スーパーファミコンの登場です。


image.png


(引用:wikipedia)

スーファミの特徴は基本的にファミコンと同じですが、ファミコンに比べて内容がリッチになっています。

以前のコントラクトは、スーファミが出ても、従来のファミコンを使って実行します。

前者の場合は人間がスーファミを使うのか、ファミコンを使うのか決めます。つまりユーザーの入力を受け取ったWebアプリケーション、もしくは新しいコントラクトのアドレスを知っている一般ユーザー自体がコントラクトに書き込みに行く際に、その際に v2 のコントラクト(スーファミコントラクト)と v1(ファミコントラクト)のどちらに実行させるのか選ぶケースです。

こうなると、面倒臭くなります。

まず、ユーザーはどのコントラクトを使えば良いのかわかりませんし、どのフォーマットでトランザクションを投げれば良いのか(どのフォーマットのカセットを使えば良いのか)わかりません。コントラクトを使うアプリケーションの管理も煩雑になります。

もし、 Web アプリケーションが全てを引き受けてデータを Ethereum に書き込みに行くと、それがパブリックチェーンの場合、Web アプリ管理者は多くの gas をパブリックの Ethereum に送ることになります。


FC アダプタというアイデア

FC アダプタとは、スーパーファミコンでファミコンのゲームを遊べるようにするツールです。

FC アダプタを使うのは、今までアプリケーションやユーザー側で担当していたコントラクトの選択をコントラクト側で判断するアイデアで、同じインタフェースを使いながら、中身のコントラクトを変えていくテクニックです。

つまり、FC アダプタというプロキシを利用することで、従来の形式のトランザクション(カセット)も新しい形式のトランザクション(カセット)もさばけるということになります。現実世界では、スーパーファミコンでファミコンが遊べるようになるアダプタや、スーパーファミコンでゲームボーイのゲームが遊べるようになるアダプタが別々に存在しますが、ここで開発するコントラクトアダプタは、過去の作品を全部遊べる万能アダプタだとします。

この方法は日本語だと下記で紹介されています。
「アップグレード可能なスマートコントラクトを実現する具体的なアプローチ」

このテクニックは一つのコントラクトを、プロキシとなるフロントエンドコントラクトとバックエンドコントラクトに処理を分けるテクニックです。

例えば、信頼できない一般ユーザーに全ての処理を持つコントラクトを実行させたくない場合に、ロジックの実行はバックエンドコントラクトが行い、フロントエンドが司令塔となることで、コアのコントラクトをセキュアに守ることができます。また、Ethereum の場合は ガスの観点でも良いことがあります。Webアプリからブロックチェーンへの書き込みを、管理者が一手に引き受けた場合、管理者が全ガスを負担することになりますが、この方法をとると、信頼できないユーザーが直接フロントにトランザクションを送る事を良しとすることができるので、管理者が負担する ガスなども全体的には低く抑えることも可能になります。

一方で、やはりコード自体はメッシーになって、管理が難しくなります。


色々挙げたけど結局難しい

このファミコンとしての捉え方はあくまでも一例です。解説の方法としてもっと適切なものがあったら教えてください。

ここで言いたいのは結局は現状のブロックチェーンだと、マーケットインした場合に頻繁にコード変更を繰り返すのって大変だよね。ということです。

かといって、ブロックチェーンにおいて DevOps が実現できないかといったらそうではありません。良い例としてマイクロソフトのブロックチェーンに関する捉え方や、方法に関しては以下のリンクに詳しく載っているので、ご興味ある方は参考にして下さい。
https://github.com/Azure-Samples/blockchain/tree/master/blockchain-development-kit/devops

ブロックチェーンでの DevOps のリリーススパンは一般的なそれよりも長くなるのではないかと思います。

ブロックチェーンにおける DevOps は、1日に何回も本番環境にデプロイすることをゴールにするのではなく、テスト環境でどんどんテストをして、ステージング環境にも何回もデプロイして、最終的にリリースするスーパーファミコンのクオリティをあげましょう。というものだと捉えるといいのではないでしょうか。

ということで、前置きが長くなり、乱暴な比較があって恐れ入りましたが、Azure のブロックチェーン DevOps の方法を紹介していきます。

この投稿では以下の順番で追っていきます。

  • ローカルで基本的な環境を準備

    • 環境設定からファイルの初期設定まで
    • Ganache をローカルに準備
    • 新しいプロジェクトの作成
    • Truffle のイニシャライズ
    • プロジェクトインポート

      • コントラクトテストコードの中身
    • ローカルでテストをして確認
    • テスト環境はフォークで作る。

      • 本番に近いテスト環境が欲しいなら、Infrastructure as Code で生成しよう
  • クラウドでテスト

    • ビルドパイプラインの中で Truffle を使う方法
    • Azure Functions の作成
    • Azure Pipeline の設定
    • リリースパイプライン の作成


ローカルで基本的な環境を準備


環境設定からファイルの初期設定まで

今回は Azure をプラットフォームに選びますが、このフェーズは基本的に Azure でなくても共通な部分です。

ローカルに環境を揃えます。

bash
# apt 最新版 
sudo apt update -y && sudo apt upgrade -y 
 
# build-essentials と python のインストール 
sudo apt install build-essential python -y 
 
# https://github.com/creationix/nvm#install-script より nvm をインストール  
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 
 
# bash をリスタートし、nvm を有効にする 
exec bash 
 
# 最新版の nodejs を用意し、nvm でアクティブにする。 
nvm install node 
nvm use node 
 
# Truffle 5 と ganache-cli をインストール (バージョンに合わせて @beta を消してください。) 
npm install -g truffle@beta ganache-cli 
 
# OPTION もし js 環境が整っている場合は、dist-upgrade パッケージをインストールして実行しましょう。 
sudo npm -g install dist-upgrade 
sudo dist-upgrade 
npm install -g truffle@beta ganache-cli 
ここで、 ganache-cli は ガナッシェと読み Solidity のテストオートメーションツールです。

truffle は、トゥラァフル ですが、日本語的にはトリュフですね。ConsenSys社 が提供するツールで、コントラクトコードのコンパイルやデプロイ、アドレス管理とかをやってくれます。


Ganache をローカルに準備

Ganache を下記よりインストールします。
https://truffleframework.com/ganache

プライベートチェーンの開発において、気になるところのひとつに gasLimit をどうするかという問題があります。多くのプライベートチェーン開発者はブロックチェーンのデフォルトを越す設定にする傾向にあります。

エンタープライズでの典型的な例としては、例えば gasLimit を 50M 程度の大きさに設定します。その変更は Ganache で下記のように設定することができます。


Screen Shot 2019-01-02 at 23.11.49.png


また、 RPC Server の IP と Network ID は今後使います。


Screen Shot 2019-01-02 at 23.13.50.png

Screen Shot 2019-01-02 at 23.13.43.png

また、Quorum のような Ethereum ベースのチェーンには拡張の機能が含まれているケースがあります。そういった拡張機能についてはまだ Ganache がサポートしていないケースが多いです。


新しいプロジェクトの作成

Azure の設定をしない方は下記で初期化しましょう。

bash
mkdir BlockchainApp 
cd BlockchainApp 
git init 
一方で、 Azure で DevOps を構築する方は、Azure DevOps で新規プロジェクトを作ります。 Azure DevOps は Azure Blockchain Workbench を使う際に最適な CI/CD ツールです。リポジトリに GitHub を使った場合の連携もスムーズです。


Screen Shot 2019-01-02 at 23.17.15.png




Screen Shot 2019-01-02 at 23.24.23.png


リポジトリはイニシャライズしてクローンします。


Truffle のイニシャライズ

ターミナルを開いて、Truffle をイニシャライズします。その際に、先ほど新規作成 / クローンしたプロジェクトのルートで操作しましょう。

VS Code の場合は  ^`  でターミナルをひらけます。

# root かどうか確認します 
pwd 
# >> /Users/hogehoge/Workspace/AzureBlockchainDevOps  
truffle init 
下記のようなファイル群が作成されているはずです。


Screen Shot 2019-01-02 at 23.39.17.png


作られた config ファイルを Ganache の値に書き換えます。環境によって network_id と port が違うはずです。 

truffle-config.js
networks: { 
    development: { 
     host: "HTTP://127.0.0.1",     // Localhost (default: none) 
     port: 7545,            // Standard Ethereum port (default: none) 
     network_id: 5777,       // Any network (default: none) 
    } 
これで、初期フォルダの準備と環境設定は完了です。

次は、サンプルのコントラクトを使いつつ、 DevOps の構築を試していきます。


プロジェクトインポート

通常であれば、このディレクトリにどんどん js のファイルを追加してテストやデプロイのスクリプトを書いていきますが、今回は、DevOps をどのように Azure で実現するかにフォーカスするので、サンプルプロジェクトを使います。
https://github.com/Azure-Samples/blockchain/tree/master/blockchain-development-kit/devops/sample-files

ここにあるファイルをダウンロードして、今のプロジェクトにオーバーライドしちゃいましょう。


コントラクトテストコードの中身

インポートしたプロジェクトのテストコードの中身は下記になっています。

test/Item.js
// コントラクトの読み込み 
const BasicItemRegistry = artifacts.require("BasicItemRegistry"); 
const Item = artifacts.require("Item"); 
 
// Workbench イベントバリデータ をもつ assert をヘルパーより追加 
require("./helpers/workbenchEventValidators.js")(assert); 
 
// ABI の定義 
const StateType = { 
  0: "Active", 
  1: "Retired", 
  Active: 0, 
  Retired: 1, 
} 
ヘルパー (workbenchEventValidators.js) の中身

.strictEqual() 関数を定義しているのがわかります。

繰り返し使いたい物に関しては、基本的にこのように外だしします。

helpers/workbenchEventValidators.js
function assertContractUpdateEvent(assert) { 
  assert.eventIsWorkbenchContractCreated = function (log, applicationName, workflowName, originatingAddress) { 
    assert.strictEqual(log.event, "WorkbenchContractCreated"); 
    assert.strictEqual(log.args.applicationName, applicationName); 
    assert.strictEqual(log.args.workflowName, workflowName); 
    assert.strictEqual(log.args.originatingAddress, originatingAddress); 
  } 
 
  assert.eventIsWorkbenchContractUpdated = function (log, applicationName, workflowName, action, originatingAddress) { 
    assert.strictEqual(log.event, "WorkbenchContractUpdated"); 
    assert.strictEqual(log.args.applicationName, applicationName); 
    assert.strictEqual(log.args.workflowName, workflowName); 
    assert.strictEqual(log.args.action, action); 
    assert.strictEqual(log.args.originatingAddress, originatingAddress); 
  } 
} 
 
exports = module.exports = assertContractUpdateEvent; 
先ほどの assert ヘルパー は下記のように使われています。
assert.strictEqual(manufacturer, accounts[0]);

test/Item.js
contract("Item", function (accounts) { 
  describe("constructor", function () { 
    let item; 
    before("deploy fresh contract", async function () { 
      item = await Item.new( 
        accounts[0], 
        "item0", 
        "microsoft", 
        "windows", 
        "blue", 
        "1985-11-20", 
        "redmond" 
      ); 
    }); 
 
    it("should have a manufacturer address", async function () { 
      const manufacturer = await item.Manufacturer(); 
      assert.strictEqual(manufacturer, accounts[0]); 
    }); 
   // ... (省略) 
  }); 
}); 


ローカルでテストをして確認

solidity のファイルのコンパイルをします。

bash
truffle compile 
# Compiling ./contracts/BasicItemRegistry.sol... 
# Compiling ./contracts/Item.sol... 
# Compiling ./contracts/Migrations.sol... 
# Compiling ./contracts/lib/String.sol... 
# Compiling ./contracts/lib/WorkbenchBase.sol... 
# Writing artifacts to ./build/contracts 
web3-providers-http がないとか怒られたら npm i --save web3-providers-http しましょう。

テストをランします。

bash
truffle test 
 
# Using network 'test'. 
#  
#   Contract: BasicItemRegistry 
#     constructor 
#       ✓ should be nameable 
#       ✓ should be describable 
#       ✓ should start off in the Created state 
#       ✓ should emit a WorkbenchContractCreated event 
#      
#   Contract: Item 
#     constructor 
#       ✓ should have a color 
#      RegisterItem 
#       ✓ should register itself successfully (281ms) 
#     Retire 
#       ✓ should change state to Retired (104ms) 
#   ... ところどころ省略   
#   37 passing (5s) 
次に truffle migrate でデータを流し込みます。

truffle migrate --network=ganache 
 
# Starting migrations... 
# ====================== 
# > Network name:    'ganache' 
# > Network id:      4447 
# > Block gas limit: 50000000 
#  
#  
# 1_initial_migration.js 
# ====================== 
#  
#    Deploying 'Migrations' 
#    ---------------------- 
#    > transaction hash:    0x9dbc6a5484e2a6c78c92f6089c704e5c445acd157d22d37e35dc8fff005552db 
#    > Blocks: 0            Seconds: 0 
#    > contract address:    0x1bcfB3862aFc5c401f8C35FCA79eE804e7962463 
#    > account:             0x4dA28037D420b50897350C05Da59a4CcFB14888E 
#    > balance:             99.46191292 
#    > gas used:            269607 
#    > gas price:           20 gwei 
#    > value sent:          0 ETH 
#    > total cost:          0.00539214 ETH 
#  
#  
#    > Saving migration to chain. 
#    > Saving artifacts 
#    ------------------------------------- 
#    > Total cost:          0.00539214 ETH 
このテストの流れでは、コントラクトをデプロイし、データを流し込んでいます。その結果、address などが生成されているのがわかります。

ちなみに、Solidity のテストやデプロイなどですが、基本的には下記のように async/await を使って書きます。

javascript はそもそも非同期処理が得意な言語ですが、テストやデプロイなどの処理は順番を気にする必要があるので、意図した動きを確認したいテストがある場合には非同期的な書き方をしないようにします。

async/awaitを使って書きましょう
module.exports = async function(deployer) { 
  await deployer.deploy(BasicItemRegistry, "BasicItemRegistry", "A registry for basic items :-)"); 
  const registry = await BasicItemRegistry.deployed(); 
  await registry.OpenRegistry(); 
}; 


テスト環境はフォークで作る

テストにおいては、テスト環境と本番環境の差を最小限にして、なるべく本番に近い環境でテストをすることが望ましいです。

Blockchain 開発においてはフォークによってそれを実現します。フォークは ganache-cli を使って簡単に実現できます。

bash
ganache-cli --fork HTTP://127.0.0.1:7545 
 
# Ganache CLI v6.2.5 (ganache-core: 2.3.3) 
#  
# Available Accounts 
# ================== 
# (0) 0x6fd15123456789234567324564554312916 (~100 ETH) 
# ... 
# Private Keys 
# ================== 
# (0) 0x8d9b29ac3e00d12345678901234567890123456789012345678902134e 
# ... 
# HD Wallet 
# ================== 
# Mnemonic:      bless access betray upon giraffe symptom exchange shock clerk reform leave mandate 
# Base HD Path:  m/44'/60'/0'/0/{account_index} 
#  
# Gas Price 
# ================== 
# 20000000000 
#  
# Gas Limit 
# ================== 
# 6721975 
#  
# Forked Chain 
# ================== 
# Location:    HTTP://127.0.0.1:7545 
# Block:       0 
# Network ID:  4447 
# Time:        Thu Jan 03 2019 00:34:09 GMT+0900 (Japan Standard Time) 
#  
# Listening on 127.0.0.1:8545 
 
もし、特定のブロックでフォークしたかったら下記を使いましょう

bash
ganache-cli --fork HTTP://127.0.0.1:7545@12345 #(ブロックナンバー) 


本番に近いテスト環境が欲しいなら、Infrastructure as Code で生成しよう

この時点ではまだ Ganache を使いフォークされたブロックチェーンでテストをしていますが、本番環境を運用していくと本番と同じインフラ環境でテストしたくなります。その場合は Azure では ARM Template を使ってインフラを再現します。

しかしテスト環境といっても、基本的には本番に近い環境が望まれます。つまり、テスト環境のセキュリティも高レベルなものが求められるということです。そのため、下記は重要チェック事項です。

  • ARM Template で RBAC (Role Based Access Control) の設定がされているか

    • 正しい権限が割り振られている環境が必須です。社内犯行を防ぐため、操作ミスを防ぐために RBAC で権限の割り振りをしましょう。
  • NSG (Network Security Group)

    • L4 のファイアウォールの設定がされているか。特に Azure でブロックチェーンを構成する場合は、VM がいくつか構成され、その VM はもちろんネットワークに所属することになります。 ブロックチェーンを守るためにファイアウォールの設定をしっかりしましょう。
  • PaaS のネットワーク環境

    • 特に Web App などの PaaS でフロントエンドを作る際には 注意が必要です。「Azure App Service スロットスワッピングの秘密」でも書きましたが、ブルーグリーンデプロイメントの環境を整えようとした時に、IP制限が正しくかかっているか、また App Service 認証をかけているとしたら、その設定は想定通りになっているかチェックする必要があります。


クラウドでテスト


ビルドパイプラインの中で Truffle を使う方法

大抵ブロックチェーンアプリケーションは、スマートコントラクトと会話するための機能を持っています。

それらのプラットフォームは Azure の場合、VM や Logic App、Function App などで、例えば.NET では多くの場合で Ethereum を使うためのラッピングライブラリである Nethereum を使いながら開発されているはずです。

Solidity のコード自体は、デプロイしてしまうと取り消すことができないため、慎重にテストしてデプロイする必要がありますが、例えば.NET で書かれたような アプリケーションコードは改変することができます。しかし、改変できたとしてもアプリケーションコードの動きはコントラクトに大きな影響を及ぼすはずで、テストやデプロイは基本的に Solidity のコード群と同タイミングで行われてしかるべきです。また、アーティファクト(成果物)として、特定のビルドタイミングで両方とも履歴管理されるべきです。

そのため、Solidity のコードと、そのコードとの会話を行うことになるアプリケーションコード(特にAPI部分)はビルドパイプラインで同時にビルドするようにすると上記が綺麗に実現できます。

例えば、Azure Pipeline を使った場合はビルドは以下のように行われるようになります。

以下の例では、環境を整えてテストして、Azure Functions のアプリケーションのビルドもかけています。

azure-pipelines.yml
pool: 
  vmImage: 'Ubuntu 16.04' 
 
steps: 
- task: NodeTool@0 
  inputs: 
    versionSpec: '8.x' 
  displayName: 'Install Node.js' 
 
- script: | 
    npm install 
    cd function-app/GanacheFunction 
    npm install 
  displayName: 'Install project dependencies' 
 
- script: | 
    npx truffle compile 
    npx truffle test 
  displayName: 'Truffle Compile & Test' 
 
  # azure-pipelines.js - Publish Test Results in  
- task: PublishTestResults@2 
  condition: always() 
  inputs: 
    testResultsFormat: 'JUnit' 
    testResultsFiles: '**/TEST-*.xml'  
 
- task: ArchiveFiles@2 
  inputs: 
    rootFolderOrFile: '$(System.DefaultWorkingDirectory)' 
    includeRootFolder: false 
    archiveType: 'zip' 
    archiveFile: '$(Build.ArtifactStagingDirectory)/full-$(Build.BuildId).zip' 
 
- task: ArchiveFiles@2 
  inputs: 
    rootFolderOrFile: '$(System.DefaultWorkingDirectory)/function-app' 
    includeRootFolder: false 
    archiveType: 'zip' 
    archiveFile: '$(Build.ArtifactStagingDirectory)/function-app-$(Build.BuildId).zip'<img width="1438" alt="Screen Shot 2019-01-03 at 21.33.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/131046/8e3cec2f-16d0-84a8-6007-9327123f0932.png"> 
 
 
- task: PublishBuildArtifacts@1 
  inputs: 
    pathToPublish: '$(Build.ArtifactStagingDirectory)' 


Azure Functions の作成

次に、Smart Contract と会話をすることになる アプリケーションを作ります。


Azure Pipeline の設定

Azure Pipeline の設定画面に行って、リポジトリの azure-pipelines.yml を設定します。



Screen Shot 2019-01-03 at 14.04.28.png


Queue に入れると実行され、結果は下記のようになります



Screen Shot 2019-01-03 at 14.05.28.png




Screen Shot 2019-01-03 at 14.15.18.png



リリースパイプライン の作成

リリースパイプラインの Artifacts に、先ほどのビルドパイプラインの成果物を設定します。


Screen Shot 2019-01-03 at 14.16.27.png


デプロイの設定をします。


Screen Shot 2019-01-03 at 14.22.25.png




Screen Shot 2019-01-03 at 15.11.26.png




Screen Shot 2019-01-03 at 15.11.51.png


実行してリリースが完了したら終了です。



Screen Shot 2019-01-03 at 21.33.32.png


コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)