シンプルなDAppを手元で試してテストネットまでデプロイしてみる【社内ハッカソン】

5/14に久しぶりに社内ハッカソンがありました。 コロナなどの影響で2年ほど見送りが続いていたんですが、 運営委員を再編するところから小規模ながらようやく開催できました。

ハッカソンといっても各自好きなこと・目標を決めて1日集中して何か成果物を作るみたいな感じなので、 今回は前から気になっていたDApps/ブロックチェーン周りの技術に触れてみることにしました。

もし、自分も気になっているな〜という方がいたら同じ手順を踏んでもらえると感触をつかめるかもしれません。

今回の記事についての注意点: 筆者の理解がまだ浅いことと、1日の中での調査のため、正しくない説明や記述がある可能性があります。

今回の目標

  • Web上で公開されているシンプルなアプリをCloneしてきて内部構造やContract、front-endのコードを読む
  • そのアプリを手元でコンパイル、ローカルのネットワークに接続してトランザクションなどの動きをみる
  • シンプルなアプリを修正できるところがあれば簡単な修正・カスタマイズを加えてみる
  • アプリをテストネットワークに繋いでそこでトランザクションを実行してみる(front-endはローカルで起動)

当日までに済ませていたこと・事前準備

以前から少しだけブロックチェーン・暗号通貨周りのあれこれを試したりしてましたが、 それに加えて簡単な勉強などを事前準備として行っていました。

MetaMaskのインストールや暗号通貨の送金など

この辺は2018年のCyptoKittiesで遊んでみた記事でやってました。

なんか技術書読む

これもだいぶ前だったと思うのですが、とりあえず以下の書籍を読みました。 全体像がつかめてよかったです。

Axie Infinityやってみる

去年だいぶ話題だったAxie Infinityをやってみました。 CyptoKittiesの時よりも、ブロックチェーン周りのエコシステム・Play to Earnみたいなところを体感できたきがします。

axieinfinity.com

CryptoZombiesでSolidityの勉強

Solidityの勉強のためにCryptoZombiesのプログラムを途中までやりました。 CryptoZombiesすごいわかりやすいし楽しいです。

cryptozombies.io

TODOアプリ起動確認・ローカルネット確認編

とりえあず、当日の流れやポイントを紹介していきます。

当日の前半戦は、以下の記事を参考にさせていただきました。 とてもわかりやすいハンズオン記事感謝です。 zenn.dev

サンプルアプリのリポジトリも公開されているので、実装手順は後日ゆっくり試してみることにして、 コードと構造を確認後、完成形を動かすところからやってみます。

nodeとかをinstallするのは省略です。

とりあえずパッケージinstallします。

$ yarn install

つぎに早速コントラクトをコンパイルしてみます。

$ npx hardhat compile
Compiling 1 file with 0.8.4
Generating typings for: 1 artifacts in dir: typechain for target: ethers-v5
Successfully generated 5 typings!
Compilation finished successfully

無事とおりました。

成果物がartifacts以下に出力されます。

$ ls artifacts/contracts/TodoList.sol
TodoList.dbg.json   TodoList.json

出力されたTodoList.json はABI(Application Binary Interface)と呼ばれ、 front側から呼び出す時のインターフェースになるっぽいです。

次にローカルのネットワークにノード立てます。

$ npx hardhat node
npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

Accounts
========

WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.

Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
...

無事ネットワークが立ちました。 初期のアカウントのアドレスと秘密鍵が20個表示されてます。

ネットワークはhttp://127.0.0.1:8545/をエントリーにいろいろ通信できるみたいです。 立ち上げたコンソールはそのままにしておきます。

さて、ローカルのネットワークはたちましたが、特に何も動いてないので 先ほどコンパイルしてみたコントラクトをデプロイします。

$ npx hardhat run scripts/deploy.ts --network localhost
localhost
No need to generate any newer typings.
todoList deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

デプロイできました。 デプロイ後コントラクトのアドレスが表示されます。 このアドレスはweb-front側で利用しています。

このとき、立てっぱなしにしていたネットワークのコンソール側にもログがでます。

web3_clientVersion (2)
eth_chainId
eth_accounts
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
 Contract deployment: TodoList
 Contract address:  0x5fbdb2315678afecb367f032d93f642f64180aa3
 Transaction:     0x376862b3b1bc5fa66710ca238c5ed4f1189c05d46f9a09058b1fffa2ee3b7bb4
 From:        0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
 Value:        0 ETH
 Gas used:      631868 of 631868
 Block #1:      0xf416cd7e33b9960166ece22b4c936aecad62c02888f7fcba585ce7dce76491a4

eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt

次にfront側たてます。 web3.jsが使われるのかなーとおもっていましたが、最近はethers.jsが主流なんですかね? web-frontディレクトリに移動してfront側のパッケージをinstallします。

$ cd webfront
$ yarn install

frontを立ち上げます。

$ yarn dev

いい感じに立ち上がりました! 実際にCreate Taskなどを押すと、ネットワーク側のトランザクションのログなどが流れるのを確認できます! うーんいいですね。

Web3Providerへの変更とconnect編

さて、いったん動きを確認できましたが、ここで疑問がでました。 そもそもコントラクトとしてはviewだけなら特にgas代はかからないが、 書き込みのTransactionはgas代がかかるはず...そもそもMetaMaskなどやアカウントとの連携はどうするんだ? というとこです。

これはApp.tsxのproviderを取得しているところの修正が必要なのかなと。

export const App: VFC = () => {
  const provider = new ethers.providers.JsonRpcProvider();
  const signer = provider.getSigner();
  const contract = new ethers.Contract(contractAddress, artifact.abi, provider);
  const contractWithSigner = contract.connect(signer);

JsonRpcProviderはデフォルトではhttp://localhost:8545につながりにいくんで、 そこでローカルのネットワークに接続して、getSignerでindex 0のAccount #0: 0xf39fd6...を引っ張ってくる。 これはわかるが、なんかノーコストでトランザクション動かせるのはなんでなんだろ? ここはちょっとよくわからなかった。

ひとまず、テストネットではこうはいかないと思うのでWeb3Providerに変えてみます。

const provider = new ethers.providers.Web3Provider(window.ethereum);

これでMetaMaskと連携できるはず。

試す前にMetaMaskをローカルネットに接続しておきます。 今回は元々MetaMaskで利用していたアカウントを使ってみます。 MetaMaskのネットワーク切り替えメニューの下部にネットワークを追加というメニューがあるのでそこから追加します。

RPC URLは先ほどローカルネットワーク立てたときに表示されたやつですね。 チェーンIDはどこで確認するかわかんなかったんですが、URLなどを入力したら自動で入りました。 多分定型で31337っぽいです。 通貨記号はテスト用はGOと設定するのが良いっぽいのでそうしました。

これでMetaMaskにローカルネットワーク追加できました。

このアカウントの残高はローカルネットワークでは0(GO)です。 このままでは検証ができないので、以下を参考に初期アカウントから送金するのを試してみました。

zenn.dev

以下をscripts/sendGasFee.tsを作成します。

import { ethers } from "hardhat";

async function main() {
  const transactionSend = {
    to: "0x.....[送信先アカウントアドレス]",
    value: ethers.utils.parseEther("10.0"),
  };

  const [account] = await ethers.getSigners();
  await account.sendTransaction(transactionSend);
  console.log("success");
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

以下を実行します。

$ npx hardhat run scripts/sendGasFee.ts --network localhost

送金できてますね!

さて、これでもうちょい本格的に試せると思ったんですが、ちょっと問題が。 web-frontのページにアクセスしてもMetaMaskが反応しないんですよね。 MetaMaskの左側は未接続と表記が。 確認すると接続ボタンを押してくださいとでます。

なるほど。確かに各種サービスにそういうボタンあります。 ということで以下を参考させていただき修正してみました。

developers.gmo.jp

以下を呼び出せばいけそうです。

const account = await window.ethereum.request({ method: 'eth_requestAccounts' });

とりあえず、コードを修正して呼びだすようにして、無事接続できました。

とりあえず、接続したことでコントラクトからのデータ取得も再度正常にできるようになりました。 この状態で再度トランザクションを実行してみます。

書き込みを実行するとMetaMaskが起動します! gas代も表示されます。それっぽいです!

MetaMaskの確認を承認すると、TODOリストにTaskが書き込まれ、実際にアカウントの通貨量もgas代として減ってます。

しかし、ネットワークのログを見ると以下のログがでていました。

Error: Transaction reverted: function selector was not recognized and there's no fallback function
....

これはTodoList.sol側にrecieveとfallbackのメソッドを実装したら出なくなりました。 techblg.app

ただ、コントラクト側に変更を加えて再度デプロイしたら別アドレスになってしまい、 storage値なども引き継がれてないので、どうマイグレートするといいのか、他のデプロイ方法があるのかはちょっと調べられませんでした。 今回はテストなので問題ないですが。

Counterアプリ起動確認編

この辺りで当日の成果物どうしようかなという感じに。 一旦全般の流れは確認できたのですが、 いかんせん自分がreactとかに詳しくないこともあり、 もっとシンプルなアプリの方が、なんか弄りやすそうな気がしてきました。 あと、デモ的にもそっちのほうがウケがよいかなと。

ということで、後半はこちらのカウンターアプリの手順を追うことに(こちらも記事公開ありがとうございます)。 zenn.dev

connectボタンの処理も実装済みです。 実はfront側にコントラクトeventのコールバックも実装してあるので、 トランザクション完了のタイミングなどを正確に取得して画面更新などが走ります。

手順を追う部分は省略しますが、 こちらも公開されているリポジトリからパッケージインストール、コントラクトコンパイル、デプロイ、フロント起動という順で試しました。

また、先ほどのTODOアプリの同様にトランザクション時にエラーがでたので、recieveとfallbackのメソッドを実装しました。 さらに、今度は既存アカウントに送金するのではなく、 記事の手順通りローカルで発行されたアカウントをMetaMaskにインポートすることで残高がある状態にしてみました。

テストネットデプロイ編

2つのサンプルアプリを触ってだいぶ理解も進んできたので、当初の目標であるテストネットワークにデプロイするところを目指すことに。 これでデモ的にもハクがつくでしょう。 Ethereumのテストネットワークの一つであるRopstenにデプロイしてみます。

まずはですが、先ほどと同様にMetaMaskの接続先をRopstenネットワークに切り替えます。 なんか選択肢に出てこないなーとおもったらMetaMaskの設定->高度な設定にテストネットワークを表示するオプションがありました。

このオプションを有効にすると普通にネットワーク切り替えにでてきました。

次にテスト用のEtherをFaucetに要求します。こちらを参考にしています。

zenn.dev

最初どこからだ?とまよったんですが、 Ropstenネットワークに切り替えた状態で、 Metamaskの「購入ボタン」を押すとでるメニューの下部スクロールしたところにありました。

その先に進んだrequest 1 ether from faucetボタン押すとトランザクションを発行できます。 テストネットワークといえメインネット同じPoWでコンセンサスアルゴリズムが走るので、 完了まで1~2分ほど若干時間がかかります。

ちなみfaucetのページにtransactionのリンクが表示されるので、etherscanで内容確認できます。

次は、実際にRopstenネットワークにデプロイします。 デプロイのためにINFURAに登録します。 infura.io

プロジェクトの情報を入力したら設定画面にて、 ENDPOINTSの項目でRopstenを選択しhttps://ropsten.infura.io/v3/〜の部分をメモります。

さらに、サンプルのカウンターアプリのpackages/contract/hardhat.config.jsを以下のように修正します。 アカウントのPrivateKeyはMetaMaskのアカウントの詳細->秘密鍵のエクスポートから取得できます。 絶対に流出(どこかに載せたり、リポジトリにコミットしたり)しないようにしましょう。

require("@nomiclabs/hardhat-waffle");

module.exports = {
  solidity: "0.8.0",
  networks: {
    ropsten: {
      url: "https://ropsten.infura.io/v3/[メモしたENDPOINTS URL]",
      accounts: ["MetaMaskで管理してるアカウントのPrivateKey"],
    }
  }
};

これで準備は整いました。 ネットワークを指定してデプロイします。

$ npx hardhat run --network ropsten scripts/deploy.js
deployed to: 0x36d7A108c6877Fa15D31275e890C9706452ADE51

デプロイが完了すると、gas代としてアカウントのETHがちゃんと減ってます。

またINFURA側のダッシュボードにもアクセスあることが確認できます。

よし、、、 ということで、本日の成果物、 gas代を払って数字を上げ下げする、その名も「Super Counter」が完成しました。(ほとんどコーディングはしてない)

なお、検証が目的なのでfrontはローカルで立てるだけでどこにもデプロイしてません。

まとめ

久しぶりのハッカソンたのしかったです! DApps/スマートコントラクトという概念に触れるのは、ちゃんと動くアプリケーションのレベルでは初だったので新鮮でした。 バックエンドにRDBのようなものが一切ないのに値が保存されているのは、なんか感慨深いですね。 新しいパラダイムに触れるのはプログラムを始めた時から、いつになってもワクワクします。

世間ではNFTとかもろもろ、投資、投機的な側面が強調されていいるような印象もあり、 記事を検索しても技術記事と混在しているような感じもします。

いわゆるweb3と呼ばれるサービスやシステムが、 既存の集中管理型のサービスの置き換えになるわけでもなく、 front-endが必要ないわけでもなく、 技術的な側面を見ると本質的にはどういう優位性があるのか?と疑心暗鬼になる気持ちもわかります。

ただ、アプリケーションやトークンが外部経済との接続を持つという点だったり(投機的目線の理由である通り)、 トランザクションの透明性だったり、 その上でそれらの手続きが自動実行可能など、 新しくできることの可能性が増えたのだなという感じがします。

どこかでweb1はread、web2はread-write、web3はread-write-ownと聞いて、 なるほどなーと思ったのですが、 それも踏まえてweb3やDappsは「価値」を扱っていくのが本質なのかもとも感じました。 人やコミュニティに応じて色々な価値基準がありますが、そいういうがいろいろ繋がってくんのかなというか。

と、ちょっとまとめでわかった風に書きましたが、 まだまだ理解が足りてませんので、引き続きいろいろ勉強したいと思います。 本質を理解してこれらのパラダイムで何か新しいものを生み出してみたいですね。

ポートフォリオっぽいサイトをGitHub Pages + Jekyll構成に移行した話

久しぶりの投稿です。

今回、個人の実績とかを集約していたサイトをリニューアルしたので、その手順をメモしておきます。

このサイト自体は大学院の研究室の都合で作成したものだったのですが、 その時々の技術的な流行をちょっと試してみるみたいな趣旨もあり、 構成もHerokuやAWSと10年の間でいろいろ変遷してきました。 完全にオーバースペックなので、もっと一般的な手法に切り替えます。

なお、移行とリニューアルが完了したサイトは以下になります。

https://mikinya.net/

今回の動機

今回、サイトを移行・リニューアルするモチベーションをまとめると以下になります。

  • 勉強のためにnode.jsやAWS、コンテナを使っていたが、今は目的を果たして単にオーバースペックで複雑
  • 特に専用のフレームワークなどは活用してないのでページ追加がやりにくい
  • もはやBootstrapが古く見えすぎる
  • 極微量な料金だが、無料でできるのなら節約したい

ということで、単にインフラのレイヤーを移行するというより、 見栄えやフレームワークまで意識していく流れです。

前回までの話

当該サイトは2019年1月にHerokuの環境からAWS/Docker/Elastic Beanstalk環境へ移行しました。 その時の記事がこちらです。

mikimemo.hateblo.jp

*ちなみに上記記事のFargateの見解はおもいっきし間違いです。そもそもFargateはコンピューティング・リソースを提供するもので、単純にECSとかと比較するのは検討違いでした。

約3年この構成で動いていたのか... 実は去年RIの購入とかもしていたりして、とても勉強になりました。ありがとう旧構成。

作業の方針

色々調査した結果、 GitHub PagesJekyllを利用することにしました。

Jekyllはマークダウン記法のドキュメントなどから、 いい感じに静的なWebサイトをジェネレートしてくれるライブラリです。

もともと、簡単にWebコンテンツをホスティングできる、かつ独自ドメインも設定可能な GitHub Pagesを利用しようとは決めていたのですが、 ドキュメントを読み進めていくうちに、Jekyllというものを知り、いいやん!となりました。

メモ: 静的サイトジェネレーターは世の中にたくさんあると思いますが、 今回はGitHub Pagesが公式対応しているのでJekyllにフォーカスです。

こちらの比較記事参考になります。

プロジェクト生成、ローカル確認

GitHub側のリポジトリ作成などは、済ませているとしてローカルにJekyll環境を構築してきます。

基本GitHub PagesやJekyllの公式手順に従っていきます。

docs.github.com

jekyllrb-ja.github.io

まずは、ローカルにリポジトリ用のディレクトリ作ります。

$ mkdir my-site
$ cd my-site

*ライブラリ開発とかですでにリポジトリ内に何かある場合はorphanブランチとかに作ったりすると良さそう

初期化に必要なgemをインストールして初期ファイルを生成します。

$ gem install bundler jekyll
$ jekyll new --skip-bundle .

Gemfileのjekyllの部分をコメントアウトしてgithub-pagesの項目に変更します。 このとき223のところはこのページから都度対応している最新バージョンに置き換えてくれとのことです。

#gem "jekyll", "~> 4.2.1"
gem "github-pages", "~> 223", group: :jekyll_plugins

これでbundle installします。

$ bundle install

完了したら以下でローカルサーバーを立ててみます。 http://localhost:4000にアクセスすると、それっぽいサイトが立ち上がっていることを確認できます。

$ bundle exec jekyll s

f:id:miki05:20220228205505p:plain

リモートでの確認

一旦この辺りでremoteにpushしてGithub Pagesの動作を確認してみると良いでしょう。 ただし、GitHub Pagesのデフォルトでは以下みたいな感じでサブフォルダにホストされるため、 あらかじめ設定をしておかないと、うまく表示されないケースがあります。

https://account-name.github.io/repo-name/

これらの設定のためにプロジェクト直下にあるconfig.ymlを編集します。 Jekyllではこのconfig.ymlがキモで、 このファイルに様々な定義や設定を指定することで、 全体の見栄えや動作をカスタマイズしていきます。

以下の項目を修正します。

url: https://[GitHub Account Name].github.io
baseurl: /[Repository Name]/

最終的には独自ドメインを設定するので、この設定は不要なのですが、 一時的に設定して、サイトの移行準備が完了したら再度修正します。

ちなみにですが、リポジトリのRoot DirectoryにREADME.mdを配置していると、 GitHub Pagesにデプロイした時、なぜかそれをトップページみたいな感じで認識されます。

こちらは_config.ymlのexcludeの項目のコメントアウトをはずしてREADME.mdを追記するとJekyllの処理対象外にできます。

exclude:
  - .sass-cache/
  - .jekyll-cache/
  - gemfiles/
  - Gemfile
  - Gemfile.lock
  - node_modules/
  - vendor/bundle/
  - vendor/cache/
  - vendor/gems/
  - vendor/ruby/
  - README.md #追記

自分の場合、作業観点でのmemoなどをREADMEに残しておきたかったので、このような処置をとりました。 Jekyllの構成フォルダをサブフォルダとかに切り出す設定なども試しましたが、あんまりうまくいかなかったです。

テーマの設定

Jekyllはサードパーティ製のものも含めてさまざまなテーマを利用できます。 今回は一番スターがついていて良さそうな感じのMinimal Mistakesを利用することにしました。

mmistakes.github.io

またテーマのライブラリごとにお作法が変わってきたりもするので、 以後はMinimal Mistakesのドキュメントも必読です。

とりあえず、テーマ導入します。 複数の方法があるようですが、今回はremote_themeを利用する方法にします。

ますGemfileに以下を追記してbundle installします。

gem "jekyll-include-cache", group: :jekyll_plugins

次に_config.ymlを修正します。skinも選べるので後でゆっくり検討するとよいです。

# theme: minima # 消す 
remote_theme: mmistakes/minimal-mistakes@4.24.0
minimal_mistakes_skin: "default" #"air", "aqua", "contrast", "dark", "dirt", "neon", "mint", "plum", "sunrise"

plugins:
  - jekyll-feed
  - jekyll-include-cache # 追加

多分これでサーバー立て直すとテーマが適用されているかと思います。 ちなみに基本Jekyllはコンテンツを修正すると自動でhtmlファイルなどを再ジェネレートしてくれるんですが、 configファイルを修正したときはサーバー再起動が必要のようです。

f:id:miki05:20220228205603p:plain

レイアウトとFront Matter

テーマを変更後、サーバーのたてる時にいくつかのワーニングがでており、 例えばabountなどのページがうまく表示されなくなります。

これは、デフォルトのテーマではpageというレイアウトが存在していたのですが、 Minimal Mistakesに変更したことでそのレイアウトが存在しなくなってしまったためです。 これらのレイアウトは各マークダウンファイルの冒頭で指定ができます。 layoutでpageが指定されているところをsingleに変更します。

---
layout: page # -> single
title: About
permalink: /about/
---

# 本文...

この冒頭のハイフンで囲まれた領域をFront Matterと呼び、 ページごとにさまざまな指定ができます。

config.ymlなどをいじくる

この辺りで_config.ymlやファイル構成を用途に合わせて修正していきます。 今回はabout.markdownやpost(ブログのような記事を管理できる機能)などの要素は不要のため、ファイルを削除しました。 また.markdownの拡張子も.mdに統一しました。

Authorの表示

今回はポートフォリオサイトみたいなものを作りたいので、自分の情報を表示するために Authorの機能を利用しました。これを使うとページの左側に情報を表示することができます

以下のように_config.ymlに追記します。

author:
  name: "Mikito Yoshiya"
  avatar: "/assets/img/avatar.png"
  bio: "Software Engineer"
  location: "Tokyo, Japan"
  links:
    - label: "Blog"
      icon: "fas fa-fw fa-link"
      url: "https://mikimemo.hateblo.jp"
    - label: "Twitter"
      icon: "fab fa-fw fa-twitter-square"
      url: "https://twitter.com/mikito0521"
    - label: "Facebook"
      icon: "fab fa-fw fa-facebook-square"
      url: "https://www.facebook.com/mikito.yoshiya"
    ...

画像などのファイルはassetsなどのフォルダに配置しておけば、 Jekyllが自動的に認識して、成果物フォルダである_siteにコピーし参照できるようにしてくれます。

ちなみに各サービスのアイコンはドキュメントにもありますがこちらで利用可能なものを検索できます。

この設定後各ページのFront Matterで

author_profile: true

とすると、有効になります。

defaultsの設定

author_profileの設定は各ページのFront Matterで指定すれば有効になりますが、 全てのページで指定するのは面倒です。

config.ymlではパスごとにFront Matterのデフォルト値を設定できます。 基本的なページではauthor_profileを有効に、layoutはsingleを利用するようにしておきます。

defaults:  
  - scope:
      path: ""
      type: pages
    values:
      layout: single
      author_profile: true

Feedはいらない

今回はFooterのRSSフィードのリンクは不要だったので、 以下のように非表示しました。

atom_feed:
  hide: true

その他

その他navigationとかfooterとかめちゃくちゃ機能があるのですが、 公式ドキュメントを読みながら進めるのがよいと思います。

今回は、超シンプルなサイトを構築するのが目的のため上記あたりの設定で完了です。

あとはindex.mdとか必要なページをマークダウンで記述していきます。 この辺りのドキュメントも参考になります。

独自ドメインの設定

基本はGitHub Pagesの公式手順に従っていきます。

GitHub上でSettings->PagesのCustom domainに取得済みドメインを入力してSaveを押します。 そうするとリポジトリにCNAMEという名前のファイルが自動的にコミットされます。

次にDNS側の設定をします。 自分の場合ドメイン自体はお名前.comで取得しています。 また、旧構成ではAWSのサービスに管理を寄せており、 DNSサービスにRoute 53を利用していたのですが、 そちらでの管理は廃止しお名前.com側に登録するようにします。

これに関しては、こちらの記事を参考にさせていただきました。

上記の手順だけでは、Github Pagesがワーニングを吐いてきたので、 お名前.comのインターフェースからwwwサブドメインのCNAMEレコードも登録しました。

CNAME www.my-site.net github-account-name.github.io

設定が完了しGitHub Pages上でcheckがpassしたらEnforce HTTPSも有効にしておきます。

faviconの設定

一応ですがfaviconの設定もしました。

faviconっていまの世の中すごい面倒なことになってるんですね... faviconを設定するためにはJekyllのレイアウトテンプレートを修正する必要があるんですが、 そこにはrealfavicongenerator.netというサービスを使ってくれと記述されています。

realfavicongenerator.net

こちらは一つのソースとなる画像をアップロードすると、必要な素材をプレビューしながら一括で作ってくれるし、 配置するためのコードスニペットも出力してくれるし、 さらにサイトのfaviconが様々なデバイスで正しく表示されるかをチェックしてくれるという神ツールです。

今回faviconも一新しようと思い新しく適当に書きました。 (今の自分はレベル1だけど簡単なベクター画像が描ける...!)

適用の手順はこちらを参考にさせてもらいました。

まず、リポジトリに_includes/head/というディレクトリを作ります。 そして、custom.htmlを配置します。 こちらは、本来ライブラリ側のソースをオーバーライドする形なのでリポジトリとかからコピーしてくるのが良さそうですが、 customと書かれているものはコメントのみのようなので、直接ファイルを作成しても良さそうです。

そして、そのファイルにrealfavicongenerator.netで出力された情報を貼り付けます。 この時、変換されたアイコン群は数が多いため、自分は整理の意味でassets/icon以下に配置しました。 そのため、hrefの部分を置き変えています。こちらの変更はbrowserconfig.xmlとsite.webmanifestの内容に対しても同様です。 また、browserconfig.xmlとsite.webmanifestに関してはその立ち位置上、サイトのルートに配置するようにしました。

<link rel="apple-touch-icon" sizes="180x180" href="/assets/icon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icon/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/assets/icon/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">

設置後https://realfavicongenerator.net/ でチェックします。 オールグリーンになればOKです。

旧環境のストップ

移行確認完了後、旧環境の停止作業を行います。

  • Route 53/ホストゾーン削除
  • Elastic Beanstalkの環境削除

Elastic Beanstalkに関してはDockerのプラットフォームが もれなくDeprecatedなってました。 今回の作業でこのような管理もフルマネージドに移行できるのはとても楽です。

その他、今回の作業の動機の一つですが、RIが2022/2/21で切れてます。 t3.nanoのRIなんて他に誰が使うんでしょうね...

コストとしては旧環境は合計で月$3.2程度でした。

その他の関連サービスも確認しましたが、問題なさそうでした。

  • EC2
  • ECS
  • ECR

作業を終えて、最後に

ひとまず移行とリニューアルが完了してひと段落です。 旧構成であるAWSサービスやコンテナを利用した取り組みは、とても勉強になるものでした。 ミニマムなものであっても実際に公開するところまでやっていくと、 ドキュメントを読むだけではわからない体験があります。

ただ、旧構成はコスト極限まで切り詰めるという方針から、 SSL証明書の管理周りがかなりテクっていて汎用性はなさそうでしたけど。

別の機会でAWSを使いこなして何かちゃんとサービスを自分自身で作ってみたいなーと思ったりしました (なかなか仕事では触れる機会がない)。

あとGitHub Pages、Jekyll、Minial Mistakesは可能性を感じました。 またどこかで活用する時がある気がします。

そして、サイト自体ですが、 本当は最初、学生時代の論文とか載せるのやめようかなと思ってましたが、 当時すごく頑張って仕上げた(&賞もいただいた)ことを思い出して、 引き続き掲載することにしました。

趣味で作ってたケーキ画像は廃止しました。

そして去年6月に執筆したUnity2021 3D/2Dゲーム開発実践入門のリンクも追記しました (ソシムさん全然宣伝できず、すみません。。。)。 こちら改訂作業も相変わらず苦労したので、ご興味あればぜひ覗いてみてください。

せっかくリニューアルしたのでポートフォリオを充実できるよう、 今後はもっともっとアウトプットしていきたいです!

Unity 3D/2Dゲーム開発実践入門の電子版が発売されます。無料試読版も配布!

先日発売・紹介させていただいたUnityの入門書 「Unity 3D/2Dゲーム開発実践入門 Unity2019対応版」の電子版が3/6に発売されることになりました! 紙の書籍と合わせて、こちらもよろしくお願いいたします。

Unity 3D/2Dゲーム開発実践入門 Unity 2019対応版

Unity 3D/2Dゲーム開発実践入門 Unity 2019対応版

また、本書の構成や内容をもっと知ってもらうために、 無料試読版をPDFにて配布させていただくことになりました! 以下からダウンロードができます。

bit.ly

無料試読版には以下の内容が含まれています。

  • はじめに〜目次
  • Chapter3-4:スクリプトによる重力の操作
  • Chapter4-6:エフェクトの表示(冒頭のみ)
  • Chapter5-3:3Dプレイヤーキャラクターの構築
  • Chapter6-3:アセットのインポートと設定
  • Chapter7-2:AndroidのKeystoreの作成

f:id:miki05:20200303091603p:plain f:id:miki05:20200302230528p:plain

こちらの試読版により、より多くの人に本書とその雰囲気を知ってもらえたらいいなと思っています。 ぜひ、お知り合いの方に広めていただけると幸いです。

Unity入門書の改訂版を書きました。

この度、Unityの入門書「Unity 3D/2Dゲーム開発実践入門 Unity 2019対応版」を執筆しました。 こちらは以前執筆した「Unity5 3D/2Dゲーム開発実践入門」の改訂版になります。 全国書店では本日2/20くらいから、アマゾンでは明日2/21に発売で、 ページ数やコラムは増量しましたが、お値段は据置でとてもお得です。

Unity 3D/2Dゲーム開発実践入門 Unity 2019対応版

Unity 3D/2Dゲーム開発実践入門 Unity 2019対応版

  • 作者:吉谷 幹人
  • 出版社/メーカー: ソシム
  • 発売日: 2020/02/21
  • メディア: 単行本

ということで、改訂を経た本書のおすすめポイント等を紹介したいと思います。

特徴1 :Unityの基礎知識に特化!体系的に学べる!

本書は、読者の方にUnityユーザーとして「ひとり立ち」してもらうことを目的にしています。 「ひとり立ち」とは、何か目標がある時に、 知っている知識をベースにいろいろ試してみたり、 適切なドキュメントを読みにいったり、コミュニティで質問したりして、 最終的には実現することが出来る状態です。 逆に「ひとり立ち」できていない状態というのは、 「何をしていいか・何から始めていいかわからない」状態ともいえます。

Unityは本当に素晴らしいゲームエンジンで機能も豊富なのですが、 それ故に覚える機能や操作がたくさんあるのも事実です。 また、ゲームというジャンルはRPG、アクション、パズル、2D、3D、VRなど、多岐に渡る技術や作り方が存在しています。 そのため、単に特定の機能やジャンルの作り方にフォーカスした説明では、 幅広い要件に対応出来る力が身につきにくいと考えています。

そこで、本書では出来る限りUnityやゲーム開発のコアとなる部分にトピックに絞り込み、 それらを、適切な広さと詳しさで体系的に説明することにしました。 C#の文法や、特定ジャンルのゲームロジックを細かく解説するよりも、 純粋なUntiyの知識の部分を450ページの中に「ギュッ」とつめこんであります。 それらも、これからUntiyと共に過ごす方(例えば自分でゲームを作ってリリースしたいとか、ゲームエンジニアとして働くとか) に必要だと思う機能を一覧化した後、ピックアップして構成を練っています。

全体の流れと紹介・利用機能を以下にざっくりまとめてみました。

f:id:miki05:20200220003720p:plain

また、発展的な内容や機能もコラムとして掲載しました。 これらのコラムでは詳しい利用方法までは紹介していませんが、 「こんな機能あるんだー」ぐらいにみてもらうことで、 いざ必要になった時のとっかかりにしてもらえたらと思っています。

特徴2:それっぽいゲームが作れる!

本書では、学習の途中で作成するサンプルゲームが「面白いこと」を重要視しました。 本書のコアコンテンツは4つのサンプルゲームをつくりながら、 Unityの機能や作法を学んでいくストーリーになっています。 4つのサンプルは、ほぼフルスクラッチで作成しますが(コードも全て誌面に掲載してあります)、 それなりにゲーム性があったり見栄えがよかったりします。 出来上がったゲームを自分のスマホに入れて、 他の人に遊んでもらいたくなる程度のものが仕上がります。

f:id:miki05:20200217003655p:plain

「面白い」を作るという、ゲーム開発はちょっと特殊な行為のように感じます。 言い方を変えると「面白い」と向き合う行為かと。 誰しもがゲーム開発をしていると「作っているこのゲームは面白いのだろうか?」と思うことがあるはずです。 それでも、開発を進めてリリースして、誰かに楽しんでもらうところまで漕ぎ釣られるのは、 「面白い」を作ることが「面白い」と感じるからだと思います。 そんな、ゲームを作るという面白さを体験してもらわなければ入門書として意味がない!という思いから、 サンプルのゲームは面白い・だけどちゃんとUnityの機能も学べるということを目指しました。

特徴3:ゼロからリリースまでアプリ開発事情をカバー!

本書のもう一つの特徴は、 スマートフォン向けの開発を全面的にカバーしているということです。 決してそれ以外のプラットフォームで参考にならないわけではないですが、 モバイルプラットフォームでリリースまでに必要なトピックをふんだんに取り入れています。

  • 実機環境構築
  • ゲームデザイン
  • インプット制御/加速度センサー
  • マルチアスペクト・解像度対応
  • リリース設定(アイコン・スプラッシュ・64bit対応)
  • 本番ビルド・ストア提出
  • セーフエリア対応
  • 動画広告の埋め込み

実際にリリースのことを考えると、 プラットフォームとの連携や事情を考慮しながら進める部分が結構あります。 これらは、あんまりまとまった情報としては無いように感じますし、 数年おきに新しい概念が登場したりしてキャッチアップが大変です。 Unityを利用すると、さっとスタンドアローン向けでexeやappをビルドすることができますが、 つまずいてしまう部分ってこういう泥臭いところだったりするんですよね。 本書は、そんな泥臭さの部分を代表的なプラットフォームかつ、 お手軽に試せるモバイルを例として、かなり実践的に扱っています。

特徴4:「これからも」Unityのスタンダードに対応!

本書はUnity2019.3をメインターゲットにしており、 比較的近年導入された「基本部分」に関わるトピックにも対応しています。

  • リリースサイクルとサプスクリプションモデル
  • Unity Hub
  • ネステッド・プレハブ
  • フラットデザイン・エディターUI

特に2018.3で導入されたネステッド・プレハブは、 全てのUnity上の作業を根本的に変化させる可能性を秘めており、 Unity2019.3でのエディターUIのデザイン変更も見た目が刷新されたインパクトある出来事でした。 本書は、数年おきに起きた全てのUnityユーザーが知るべき事柄にしっかりと追従していると言えます。

なお、タイトルには「Unity2019対応版」となっていますが、 おそらくUnity2020以降も問題なく利用できるはずです。 実は本書で紹介している物理エンジンやアニメーションなどの機能部分のトピックは、 前の版の時からあまり変更がありません。 これは、単に筆者がめんどくさくて変更しなかったわけではなく、 今も昔もUnityを使い始める上で必要な部分はそれほど違わないということだと言えます。

Unityでは、DOTSやHDRP、URP、シェーダーグラフ、Addressable Assets Systemなどなど、 どんどん新しい機能が追加されていますが、 それらはUnityの基本上に習得すべき事柄だと考えます。 本書では、単なるトレンドではなく、 今後もずっとUnityを利用していく上で最初に知るべきことに的を絞り取り上げてます。

こんな人におすすめ

以上のような特徴から、 ざっくり本書をおすすめしたい人は以下のような感じです。

  • 「Unityを触れる」から「Unityで作れる」という段階に実力を伸ばしたい人
  • これから業務でUnityを利用し始める人
  • 実際にゲームを開発してストア公開までを視野に入れている人

「プログラムに不慣れな方にはレベルが高いかも」という意見もいただいていますが、 すべての手順・コードを掲載・解説していますので、 写経を進めていけば問題なくサンプルを完成させられると思います。

f:id:miki05:20200220083728j:plain

執筆の苦労話とか

前の版「Unity5 3D/2Dゲーム開発実践入門」 は苦労した甲斐もあって、皆様からとても評価していただきました。ありがとうございます。 新卒の方から、学校の教科書でしたとか、知り合いの大手ゲーム企業で配布されたという話も聞きとても驚きました。

そういうことから、 本書の「しっかり体系的にUnityを学べる本」という立ち位置のようなものを自覚しました。 しかし2015年の発売からUnityやプラットフォームまわりの事情もだいぶ変わり、 情報がだいぶ古くなってしまったことに、ずっともやもやを感じていたのです。 そのような中で、2019年2月に本格的な改定の話をいただき作業を開始しました。

改定作業では、 最初に全体の修正内容を見繕いましたが、 サンプルゲーム自体を別ゲームには変更しないことにしました。 これは、 Unityを体系的に学ぶための機能を3章から6章にかけて重複なく利用し、 コード量を抑えながら面白いゲームにするという構造を、 緻密に設計した結果があの4つのサンプルだったからです。 ゲーム内容を変更して それらすべてのパズルを再度組み直すことはできなそうだなと...

とはいえ、サンプルゲームの方針やロジックはそのままでも、 アセットの見栄えを大幅にリファインし、 Unityの新機能などへの対応を果たし、 手順や説明もより分かりやすくなるように検討を重ねました。

そんなこんなで、 サンプルゲーム自体は変わっていないですが、 Unity新機能の検証や、紹介している機能の確認、 操作フローや説明自体のリファイン、 さらには2019年内で、どんどん更新されていくUnityの情報への対応などで、 丸々1年間かかったのでした。。。しんどかった

ただ、Unity Hubや2018.3のネステッドプレハブ、 2019.3のUIの刷新などの情報を含めて改定を実現できたのは、 とても良いタイミングだと思っています。

さて、本書最大のバージョンアップ点を最後に....

f:id:miki05:20200216190209p:plain

6章のアザラシの解像度が爆上がり!! ファミコン→プレステぐらいの進化はあるでしょう。

謝辞

サンプルのリファイン、カバー書き下ろしにご協力いただいたGaryuさん、いつもの事ながらありがとうございました。 また、担当のソシム木津様、締め切り間際等でも色々無茶なお願いを聞いてくださり、ありがとうございました。

そして、家事の負担とアザラシのイラストに協力してくれた妻に感謝。

ElectronとReactでAPI叩いて何か表示するアプリを試してみたメモ

先日、会社の業務外開発イベントに参加してきた。 これは、自分が新卒の頃からやっているイベントの後継で、 テーマ自由でもくもく作業するハッカソン、所謂もくもくそん。

今回は、ずっと昔から気になっていたElectronとReact.jsを使ってみることにした。 大半はJavaScript力を向上したいという思惑がある。

作るもの・使うもの

Redmine上の自分のタスクを、取得してきて表示するだけのアプリ。

ViewのフレームワークReact.jsを利用して、 自社の Redmine Rest APIのRestAPIを叩いて表示。 それらをElectronでアプリ化する。

Vue.jsとどっち試そうか迷ったが、 なんかいろいろ応用が効きそうなReact.jsにしてみることに。

Step0: nvm, node Install

まずnvmをいれる

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash

そして、とりあえずnodeの安定版いれる

$ nvm install --lts
$ nvm use --lts

Step1: Scaffold

React.jsのテンプレートプロジェクトをcreate-react-appでつくる

こちらの手順を参考させていただいた。 https://qiita.com/chibicode/items/8533dd72f1ebaeb4b614

create-react-appインストール

$ npm install -g create-react-app

プロジェクト生成

$ create-react-app redmine-desktop

できたプロジェクトはすでにgitリポジトリになってる。楽。

Step2: Electron化

こちらの手順を参考にさせていただいた。というよりほぼそのままの手順。

https://medium.com/@impaachu/how-to-build-a-react-based-electron-app-d0f27413f17f

以下パッケージを追加

$ npm install cross-env electron-is-dev

以下パッケージをdev環境に追加

$ npm install -D concurrently electron electron-builder wait-on

以下electron.jsをPublic以下に配置

const electron = require("electron");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require("path");
const isDev = require("electron-is-dev");
let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({ width: 320, height: 640 });
  mainWindow.loadURL(
    isDev
      ? "http://localhost:3000"
      : `file://${path.join(__dirname, "../build/index.html")}`
  );
  mainWindow.on("closed", () => (mainWindow = null));
}

app.on("ready", createWindow);
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (mainWindow === null) {
    createWindow();
  }
});

以下をpackage.jsonに追加

...
"description": "",
"author": "Mikito Yoshiya",
"build": {
    "appId": "net.mikinya.redmine-desktop"
},
"main": "public/electron.js",
"homepage": "./",
...

package.jsonのscripts項目を以下に変更

...
"scripts": {
    "react-start": "react-scripts start",
    "react-build": "react-scripts build",
    "react-test": "react-scripts test --env=jsdom",
    "react-eject": "react-scripts eject",
    "electron-build": "electron-builder",
    "release": "yarn react-build && electron-builder --publish=always",
    "build": "yarn react-build && yarn electron-build",
    "start": "concurrently \"cross-env BROWSER=none yarn react-start\" \"wait-on http://localhost:3000 && electron .\""
},
...

この時点で

$ npm run start

でアプリが開発モードで起動するように

$ npm run build

でapp化してdist以下にappと配布用のdmgを出力できるようになった。

Step3: Presentation, React Component構築

ビューのレイヤーを修正

公式のチュートリアル等を参考にごにょごにょいじった。

CSSも適当にbootswatchのcss.minを入れた。

cssはただ配置してApp.jsからimportしただけだけど、これでいいのだろうか。

できたReactのコンポーネントは以下みたいな感じ。

import React from 'react';

class TaskCard extends React.Component {
  render() {
    return (
      <div className="card text-white bg-primary mb-3" >
        <div className="card-header">{this.props.title}</div>
      </div >
    );
  }
}

export default TaskCard;
import React from 'react';
import ReactDOM from 'react-dom';
import './bootstrap.min.css'
import TaskCard from './TaskCard'
import IssueUseCase from './useCase/IssueUseCase'

class App extends React.Component {
  constructor(props) {
    super(props);
    this.useCase = new IssueUseCase();
  }

  render() {
    this.useCase.getOwnIssues(issues => {
      var list = [];
      for (var i in issues) {
        list.push(<TaskCard key={issues[i].id} title={issues[i].subject} />);
      }
      ReactDOM.render(list, document.getElementById('issues'));
    });

    return (
      <div className="container">
        <div className="row">
          <div className="col-lg-12">
            <h2>Tasks</h2>
          </div>
        </div>
        <div className="row">
          <div className="col-lg-4">
            <div className="bs-component">
              <div id="issues"></div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default App;

この時点ではRestAPIからcurlで叩いたjsonをファイルで保存したデータをモックデータとして、Viewに表示してみる。記法やお作法の正しいやりかたは全然不明。

うん。これでとりあえずデモはできる。

f:id:miki05:20191113010021p:plain

Step4: DataStore, API実装

実際にAPIを叩いてサーバーのデータを取ってくるようにする。 通信と非同期の処理にaxiosをつかってみる。 XMLHttpRequstのラッパーでPromiseでイベントの制御ができるらしい。

$ npm install axios

通信部分はとりあえずトークンとかクエリとかベタがき。 あとでUI上から設定できるようにしよう...

import axiosbase from 'axios'

class RedmineApi {
  constructor() {
    this.axios = axiosbase.create({
      baseURL: 'https://url_to_redmine',
      headers: {
        'Content-Type': 'application/json',
        'X-Redmine-API-Key': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
      },
      responseType: 'json',
    })
  }

  getIssues() {
    return this.axios
      .get('/issues.json?limit=100&tracker_id=4&status_id=1&assigned_to_id=270')
  }
}

export default RedmineApi;

ここで二つの問題がでた。 一つめはCORS(Cross-Origin Resource Sharing)で、異なるドメインへの通信を制限するJavaScriptフロントのよくあるやつ。 現状の開発状態でもlocalhostで立ち上げてるぽいし、うん確かにそうなるわな。 でもAppにバンドルした場合はどうなるんだ? これは、以下のElectron側の設定で解決できた。

electron.jsのnew BrowserWindow();のパラメータに以下を追加

...
  mainWindow = new BrowserWindow({
    width: 320, height: 640,
    webPreferences: {
      webSecurity: false
    }
  });
...

参考:https://qiita.com/yasuflatland-lf/items/4f57b6dd311d4918e8f5

もう一つは会社で運用しているRedmineが自己証明書で、ERR_CERT_AUTHORITY_INVALIDエラーがでて、 XMLHttpRequst系ではそれを無視できる設定にできなかったこと。 curlではkオプションを追加するだけだが。

こちらも同様に、以下のElectron側の設定で解決。 electron.jsに以下を追加

...
app.on('certificate-error', function (event, webContents, url, error, certificate, callback) {
  event.preventDefault();
  callback(true);
});
...

参考:https://qiita.com/yuya-oc/items/2764bf7a33c751498858

完成

とりあえず動くものができた。 APIトークン埋め込み、ユーザーID埋め込みなので、完全個人しか使えない。 全然時間足りなかった。

所感

  • Electreon便利, React良さそう
  • JSの作法もっと知りたい
  • webpackとかなんちゃらとか、いろいろ覚えることある
  • TypeScript覚えたい
  • Rxを導入してみたい
  • JSでテストどう書くんだ
  • Electreon上のプロジェクトでClearn Archtectureを適用してみたい
  • Appまでビルドして配布形式までとりあえず1日で走りきれたので満足
  • たまには違ったコンテキストの作業は刺激になる
  • 割と簡単に改良できそうなので、この後少しいじってみることにする

Unity Excel Importerをv1.1にバージョンアップ

ちょっと時間があったので、 去年作ったUnity上でエクセルをインポートするプラグインをアップデートした。

github.com

Release Note

  • SerializeFieldアトリビュートをサポート(非パブリックなフィールドでも利用可能に)
  • 空のセルを基本Default Valueとして扱う
  • Excel Assetのクラス名と実際のエクセルファイル名のひも付きをオプションで変更可能に
  • インポート時にログを出力するオプションを利用可能に
  • NameSpace付きのExcelAssetにも対応
  • インポートエラー時にシート名も表示

このプラグインは、 以下のUnityプログラミングバイブルのマスターデータ構築部分を執筆する際に作成したプラグインで、 結構丁寧に考えながら設計している(つもり)。

Unityゲーム プログラミング・バイブル

Unityゲーム プログラミング・バイブル

導入の仕方は以下 qiita.com

が、しかし、Unity Excel Importerでしらべると、 大御所テラシュールさんのExcel Importer Makerも候補に表示されググラビリティが低い。 Excel Importer Makerの紹介記事は結構でたりして少し寂しい。。。

そもそも、このプラグインを作った背景には、 いくつか自分の思う思想でエクセルのインポートを扱いたいという思いと、 書籍で紹介する上では、それらの機能に責任を持ちたいと思ったからだ。

もうちょい、いろんな人に使ってほしいので紹介する。

Unity Excel Importerの思想とかメリット

以下の三つが主な特徴だとおもう。

1. シンプル&高いポータビリティ

まず、一番重要視したのは、シンプルさ。メインのソースはExcelImporter.csのみ。 一部Editor上から導入をサポートするコードもあるが、そのあたりはなくても動く。 Editor拡張を利用しない場合は、数行のコードを自前で書けばよい。

ほぼEditor拡張を使わないようにしたことで、癖がなくどんな案件でも導入しやすいはず。 Unity Editorのメニューをほとんど汚すこともないし、冗長なコードが増えることもない。

また、Excel内のテーブルのスキーマは利用者がソースコードで定義する(Enityソース)。 これにより、以下二つのメリットがうまれる。

  1. jsonの様なシリアライズ形式にそのまま移行できる
  2. エンジニアはソースをいじるだけでいい(GUIをぽちぽちしなくてよい)

1.に関しては、結構重要。 プロジェクトの開発初期では、とりあえずエクセルでマスターデータを構築しておいて、 開発が進んだあと、サーバーから取得する形に変更するなど、 データのシリアライズ形式をスイッチするとめっちゃスムーズ。 つまり、プラグインのフローや形式になるべく依存せず、 プロジェクト内の都合を極力優先できるということ。

2.に関して、意外とエンジニアはGUIの手続きを操作するのが嫌いだったりする。 手順を間違えたり、既存のソースのコピペができなかったり、独自の自動生成フローが使えないから。

2. 必要スクリプトは自分でマネージできる

テラシュールさんのExcel Importer Makerはその名の通り、 ImporterをMakeするプラグインで、 エクセルごとにツールを使って専用のImporterを、専用の配置場所に自動生成する形になる。

反面、このプラグインは完全に「Excel Importer」でインポートロジックコードの追加生成はしないし、 スキーマを定義するソースの配置等は利用者側に委ねている。 (その代わりリフレクションをバリバリつかっているけど、RunTimeではインポート済みだから問題ない)

そのため、ちょっとしたデータ構造の変更とか、 フィールド名の変更とかも気軽にできるし、削除する際も気兼ねなくできる。

3. 実プロジェクトで培われたユースケースに準拠

実はこのプラグインのもとになったのは 2011年くらいにUnityを会社で使い始めたときに、 レベルデザインを簡単にするために作られたエクセルインポートスクリプトだった。

このスクリプトがめっちゃ便利で、 いろんなプロジェクトで秘伝のタレのように使われたり改修されてきた経緯がある。 つまり、実際のプロジェクトで運用に耐えうる実績を持つフローと言えると思う。

特にコメントアウトとかEnumの利用は結構重要だとおもう。

そして、プラグイン化した今も、 新しいプロジェクトのプロトタイプとかでも利用しているし、 データ層の仮実装として相当な開発速度向上に繋がっている(上記のとおり後でスイッチも可能)。

また、都度新しいユースケースも見つかったりして、 しばらくはちゃんと対応していく予定(今回v1.1)。

まとめ

つまり、実際の利用と運用を考えて頑張って設計したプラグインなので、 是非ともつかってみてほしいということを言いたい。

あと仕様はだいぶ出揃っているから、本当はテスト書きたい。

ちなみに宣伝になっちゃうけど、 このExcel Importerを使ってライトゲーム向けのマスターデータを構築する例を 上記のUnityプログラミングバイブルで紹介しているのでよければ参考にしてみてほしい。

シンプルなNodeアプリをDocker化してElastic Beanstalkに移行してみた

DBも使わない超シンプルなnodeアプリをHerokuで管理していたが、 いつぞやからか無料枠では月終わりにスリープするように体系がかわってしまったので移行を考えていた。

とりあえず勉強もかねて、AWSでDocker化してみることに。

今回のゴール

  • HerokuのHobbyプラン7$/月よりも低コストでホスティングする
    • その代わり最低限のスペック・冗長化でよい
  • 基本的なDockerのサービス使ってみることで使いまわせる枠組みを得る
    • ミニマムなデモアプリでの利用
    • 開発環境構築とワークフローの汎用化
  • Herokuレベルな触りごごちで簡単にデプロイ・管理できるようにしておく
  • HTTPS化する

AWS上のサービスや構成の検討

とりあえず、どんな構成で行くかをちょっと使ってみたりして比較

今回はコストが最優先。 なので、インスタンスはt3.nano1台くらいでいきたい。 ロードバランサーはいらない。お遊びでやるには高い(立てとくだけで月2000円くらいか)。

これじゃDockerのメリットなくないって? 現状はあまり無いが、負荷が増えた時にスケーリングできる構成に柔軟に変えられる点がいいかも。

次はどんなサービスを使うか。以下検討

  • Fargate
  • ECS
    • 細かく設定をできそうだが、その反面自前でいろいろやる必要が出てきそう。今回は生で扱うのはオーバースペック感。特に構成管理やデプロイフロー周りをしっかり検討する必要がありそう。
  • EKS
    • 未調査。Kubernetesは今後のメインストリームに?今回はまずシンプルな構成の勉強も兼ねるので一旦考慮外
  • Elastic Beanstalk
    • マルチコンテナで利用する場合ECS上で動作するが、その管理あたりを丸っと任せられる。デプロイも容易。

ということで、今回つかうもの&ざっくり構成。

  • 複雑なことしない+管理を楽にしたいのでElastic Beanstalkを使う。
  • せっかく&独自イメージをプライベートなところに置きたいのでECR(Elastic Container Registry)もつかう。
  • シングルインスタンスロードバランサーは使わない。
  • LBなしでSSL化に伴う証明書の管理がAWS上でできないので、インスタンス内にNginxのコンテナを立てそこに証明書を配置する。
  • ドメインはお名前.comで取得しているが、こちらもせっかくなのでRoute53のDNSサービスを利用することに。

Local Docker Install

そもそもMacを新調してローカルにDockerが入っていなかったので、以下を参考にインストール。 登録とかめんどかったのでbrew経由でいれた。

qiita.com

$ brew install docker
$ brew cask install docker

Node App コンテナ化

まずは、アプリ自体をコンテナ化。 いろいろ検討した結果、すこし古い記事だが、基本が抑えられていたので以下を参考にDockerfileや開発フローを構築。 postd.cc

ベースとなるImageは設定の楽さとサイズの兼ね合いでnodeのslimを選択

これでも220MBくらいになるんで本当はもっと最適化した方が良さそうだが。

以下のようなDockerfileを構築

また、Imageのbuild時にアプリケーションのソースを丸っとコピーするが、 モジュールのインストールはbuild時に行うのでnode_moduleはコピーの対象外にしておく。 (ローカルでnpm installしてた場合とか、問題は無いだろうが冗長なので)

.dockerignoreを作成

node_modules
npm-debug.log

この状態でも、 docker buildしてdocker runすればコンテナが起動してアプケーションに接続できる。

SSL化の準備・ローカルでのチェック環境

とりあえずSSL化のことについてちょっと考える。 まず証明書は無料で発行したい。ただALBは使わないので、とても楽なCertificate Managerとの連携がいまいち。

そこで以下を参考にhttps-portalを利用してみることにした。 qiita.com

基本はnginxのプロキシWebサーバーだが、Let's encryptから自動で証明書を取得・更新をしてくれる。 デモレベルのアプリであれば十分そう(自前で管理できないのはやりにくい面もあるが)

まずは、ローカルでhttps接続を確かめられる環境としてdocker-composeファイルをつくる。 ちなみに本番デプロイ前の確認用ということでdocker-compose.stg.ymlという名前にしている。

Let's encryptは証明の発行上限が週ごとにあるので、開発段階やチェック等で本番接続していると動かなくなる。 そこでenvironmentのSTAGEをlocalにしておく。

こうしておくと、オレオレ証明書を自前で発行して代わりに使ってくれる。

またローカルでhttps接続のチェック様にhostsを修正しておく

sudo vi /etc/hosts
...
127.0.0.1       mikinya.net.localhost
...

これでとりあえず、以下コマンドでImageのビルドとコンテナの起動ができる。

$ docker-compose -f docker-compose.stg.yml build
$ docker-compose -f docker-compose.stg.yml up

でブラウザからこのローカル用のドメインにアクセスした時に、https-portalとcontainerの連携のチェックができる。

開発環境・開発フロー

現状のままだとソースコードを編集するたびにdocker-compose buildしないといけないのでめんどい。 開発用のフローを別途用意する。

そもそも、node起動後ファイル更新検知・リロードが自動でできていなかったので、nodemonを導入した。 package.jsonのdependenciesにnodemonを追加

...
  "dependencies": {
    "nodemon": "^1.18.9",
...

そして、普段づかいのローカル開発環境用docker-composeを別途つくる。

こっちのポイントは以下

  • ローカル開発環境ではnginxのプロキシを利用しないでシンプルに単体で立てる
  • 起動コマンドを上書き、nodemonが起動する様にする
  • gitリポジトリローカルにあるソースをvolumesとしてマウントし、ローカルのソースを直接起動・コンテナ内の起動アプリをすぐに更新する
  • mode_modulesのvolumeを別途作成し、コンテナ内の領域からインストールしたmode_modulesを参照する

これで、package.jsonを更新後、コンテナ内からpackage-lock.jsonの更新が可能で、ローカルのリポジトリにコミットできる。しかもnode_modulesは開発環境では動的に更新が可能で、imageにビルドするときはpackage.json、package-lock.jsonできちんとインストールされる。

詳細はこちらを参考に

DockerでのNodeアプリ構築で学んだこと | POSTD

なお、Macだとファイルシステムの都合上、動作がめちゃくちゃ遅くなるという問題があるみたいだが、アプリケーションが小さいせいか、実用上全く気にならなかったのでそのままにしている。

もし問題がおきたら、docker-sync等を使うのが良いのかも。

qiita.com

まぁ、ひとまず、開発開始の流れがめっちゃ簡単になった。

  1. Macを買う、gitとdockerを入れる
  2. リポジトリをcloneする
  3. docker-compose up
  4. コード編集 5 git add & commit

以上、これで、locahostに開発環境が立ってソースの編集もできる。 npmもnode入れる必要もないし、npm installもいらない。楽

AWS準備

awsコマンドもインストールしていなかったのでインストール

brew install awscli

aws configureで適切な権限を持つユーザのAccess Key とかSecretとか入れる

また、Elastic Beanstalkのマルチコンテナを使う場合は何かしらのコンテナのレジストリサービスが必要。

シングルコンテナの場合はソースコードをアップロード後にbuildするみたいだが、 今回はNginxのコンテナもフロントに立てたいのでマルチコンテナでECRをつかってみる。

ECR

あらかじめECRのコンソールから適当な名前をつけてリポジトリを作成。

また、ゴミがたまるのも嫌だったのでLifecycle Policyも設定。

f:id:miki05:20190118171720p:plain これで3つ以上のタグが無いImageは消えるかな?(たぶん)

でローカルのビルドとpushが面倒だったのでスクリプトを作成(scripts/push-image.shに配置)。

Elastic Beanstalk準備

Elastic Beanstalkのコンソールから新しいアプリを作成。

プラットフォームをMulti Container Dockerにしておく。 とりあえずサンプルのアプリをデプロイする設定で、ロードバランサーなし、他はだいたいデフォルト。

構築が終わり提示されたURLにアクセスするとNginxが立っている様が見れる。 EC2に指定したインスタンスが立ち、EIPが振られ、ECSに項目として現れる。

Dockerrunファイル作成

Dockerrunファイルをつくる。基本docker-compose.ymlを単純に置き換える感じ。

ただ、独自にbuildしたアプリのimageの取得先がECRのURLになり、https-portalの証明書の取得を本番環境設定してやる必要がある。

Elastic Beanstalkロール権限の設定

Elastic Beanstalkの管理下でインスタンスからECRにアクセスしImageを取得できるようにしないといけない。

Elastic Beanstalkを利用し始めると、IAMにaws-elasticbeanstalk-ec2-roleが追加されているので、 こいつにAmazonEC2ContainerRegistryReadOnlyポリシーをアタッチする。

Route53設定

https-portalを利用していることもあって、おそらくDNS設定をしてからじゃ無いと証明書取得がこける気がする(未確認)。まぁ大したアプリじゃ無いので、一旦停止を良しとしDNSを最初に切り替えておく。

Route53にて新たに取得したDomainのCreate Hosted Zoneを作成。さらにCreate Record SetからRecordを追加

  • Type: A
  • Alias: Yes
  • Alias Target: Elastic Beanstalkで作成したアプリケーションURL

あとはお名前.comのDNS設定にNSレコードの情報を追加

セキュリティーグループ設定

Elastic Beanstalkがインスタンスに設定したセキュリティグループにのインバウンドにHTTPS/443を追加

Elastic Beanstalkデプロイ

さて、あとはデプロイ。基本的にコマンド一つでデプロイしたい。基本はebコマンドでできる。

ebコマンドをインストール&初期設定

brew install awsebcli
eb init

ただデフォルトのeb deployは、gitのアーカイブをアップロードする。ImageはすでにECRにpush済みなので、リポジトリのソースファイルはデプロイに必要ない。

大した容量にはならないが、必要なのはDockerrun.aws.jsonだけなので冗長なものはアプロードしたくない(s3にたまる)。

調べてみるとebコマンドはデプロイするものを任意指定でき、独自にデプロイのフローを制御できるらしい。ということで、自前でDockerrun.aws.jsonをzip圧縮してそれだけあげることに。

まずは、.elasticbeanstalk/config.ymlに以下を追加する

deploy:
  artifact: .elasticbeanstalk/artifact.zip

これでeb deployコマンドで.elasticbeanstalk/artifact.zipがアップロードされるようになる。

さらに独自のdeployスクリプトを用意する(scripts/deploy.shに配置)。

#!/bin/bash

set -eu

zip .elasticbeanstalk/artifact.zip Dockerrun.aws.json 
eb deploy

zip圧縮後deployコマンドを叩くかんじ。

ただ現状のデプロイフローはまだ問題がある

  • ERCへのpushスクリプトとdeployスクリプトを二回叩く必要がある
  • .elasticbeanstalk/config.ymlがignoreされているのでartifactの設定を他の端末でやり直す必要がある
  • gitのリビジョンとデプロイが紐づかない(Dockerrun.aws.jsonはコミット前でもデプロイできちゃう)

もろもろ考えると、さっき作ったimageをビルドしてECRにあげるスクリプトもデプロイスクリプトに組みこむといいのかも。 また、独自zipをつくるより.ebignoreファイルを作って、Dockerrun.aws.json以外は無視するほうがよいのかも。

開発とデプロイのまとめ

  • ローカル開発
    • docker-compose up
    • ブラウザからlocalhost:3000にアクセス
    • ファイルはそのまま編集して、コミットすればよい
  • Webフロントサーバー込みのローカルチェック
  • 本番デプロイ
    • scripts/push-image.sh
    • scripts/deploy.sh

目標コストに関して

さて、そんなこんなで、AWS上にアプリを移行しましたが、コストを計算。 ちなみに東京リージョンです。

  • EC2 : 0.0068USD/Hour * 24 * 30 = 4.896USD
  • EBS : 0.12USD/GB/Month (gp2) * (12 + 8)GB= 2.4USD
  • Route 53 : 0.5USD/HZ = 0.5USD
  • ECR : 0.10USD/GB/Month * 0.1GB = 0.01USD
  • EIP : 0USD
  • ECS : 0USD

合計:7.806USD

おっと...細かい費用がかさんで目標のHeroku Hobbyプラン7$より高くなってしまった...w

まぁあとは、 リージョンをオレゴンにするともう少し安くて、6.726USDでいけそう。 あとはリザーブインスタンスを使うってとこかな。

もう少し研究してみます。