「CircleCIのconfig.ymlを守ろうとした話」を発表しました
「【大阪】CircleCI ユーザーコミュニティミートアップ #1」に参加し、「CircleCIのconfig.ymlを守ろうとした話」という題名でLTをしてきました。
発表資料
ストーリー
組織の中でCircleCIを多数のリポジトリで使い始めたとき、config.ymlに一定のルールを課したくなりませんか?セキュリティ的な意味と、Lint的な意味の両方で僕は欲しくなってきます。
ということでconfig.yml
にどのようにガバナンスを効かせることができるのか、という試みについて話しました。主にconftest
でCIにルールを課す方法について述べています。
興味がある方はぜひ下の記事も読んでみてください!
また、ルールを書くときに用いているRegoについては、Open Policy Agentとともに以下の記事で紹介しています!
僕の発表内容には答えがなく、「みなさんはどのようにガバナンス効かせていますか?」と問うて終わりました!
いろんな人のconfig.yml
話聞きたいですねー!
おまけ
質疑応答コーナーがあったのですが、「config.ymlへのガバナンスの効かせ方で悩んでいる方いますか?」という質問に誰も反応しなかったので、僕が勝手に悩んでいるだけなのかもしれない、ということはそれとなくわかりました(笑)
そして、イベントでは「ワタシハサークルシーアイチョットデキル」Tシャツいただきましたー!感謝感謝です!
Netlifyへのデプロイがタイムアウト
最近は仕事から趣味まで作ったもの(主にフロントエンド関連)のデプロイが簡単にNetlifyにできてしまうので多用しています。
直近ではVue.jsのコンポーネントをStorybookで公開しようとしたのですが、単純なタスクなはずが思いの外ハマってしまったので対処法をシェアしようと思います。
TL;DR
問題
npm run build
のようなビルドタスクが30分以上かかってしまってNetlify側でタイムアウトしてしまう
解決方法
ビルド時のログの出力量に注意!特にwebpackのようにビルド時に大量にログを出力するツールを使う場合は、出力しないオプションを付加しましょう
Netlifyがタイムアウトする
発生していた問題とは、Netlifyがタイムアウトするというものです。
上の画面はVue.jsのStorybookをビルドしている最中のものです。静止画だけで伝えられないのですが、ビルドの進捗がものすごく遅いんです。このときは開始したのが12:45ごろだったので、およそ30分くらいこの調子でちょっとずつ%が上がるだけで、そのあと静かにデプロイが失敗します。
サポートに問い合わせ
Netlifyはエラー文言も無しに静かに失敗し、ローカルではなんの問題もなくサクッとStorybookを開始できるのでお手上げでした。ということでサポートに問い合わせしてみることにしました。デプロイに失敗すると簡単にサポートに連絡できるようにリンクが用意されています。
ここから問い合わせフォームが下のように入力できるようになっていて、Netlify側もデバッグしやすいようにリンクも自動で貼り付けてあるので、送信するだけです。
僕の場合、送信してから半日くらいでサポートから返事がきました。
内容を要約すると
「ビルドのログが多すぎて、Netlifyの Log Serviceが問題を起こしていた。Storybookのビルドするなら —quiet
オプション付加してみて」
ってことでした。
なるほど、確かにログの表示が変に空白が多かったりしてましたね。
ログ出力量に注意!
ということでbuild-storybook
に—quiet
オプションをつけることにしました。公式サイトでもドキュメントされています。
https://storybook.js.org/docs/configurations/cli-options/
更新して再度デプロイしてみると、サクッと終わってサクッとNetlify上でStorybookが公開されました!
変に長時間悩み続けることなく、サポートに問い合わせてみてよかったです!
まとめ
- Netlifyのビルドタスクのログ出力量には要注意
- 可能であればログ出力が抑えられるオプションを付加しておくこと
- NetlifyのサポートのUX最高でした
調べても同じ問題に遭遇している人を見つけられなかったので、この記事が誰かの助けになればと思います。
PHPの`$`と`->`がつらくなったらマクロを使えば良いことに気づいた
最近PHPを書くことが多いのですが、変数の前につける$
(ドル記号)とメソッド呼び出しやプロパティ呼び出しに使う->
(矢印)の指の動きの効率悪さに耐えられなくなりました。。。
PHPStormを使っているといい感じに$
を入力しなくても変換してくれたりするのですが、その動きもまた気持ち悪く感じてしまうのでどうしたものかと思っていました。
そこで今更ながら発見したのがエディタの「マクロ」機能です!
マクロ作成
PHPStormであれば上のキャプチャのように「Edit > Macros > Start Macro Recording」を実行することでマクロの記録を始めることができます。
記録中は下図のようなインジケーターが表示されます。
ここでマクロとして入力したいキーを押します。$
用のマクロを作りたいので$
を入力すると、記録されているのがわかります。
そして赤い■をクリックするとマクロの記録が終わるので、このマクロに名前をつけてあげます。わかりやすく$
にしておきます。
これでマクロは完成です!
同じ要領で->
も作っておきましょう。
マクロのショートカット作成
マクロが完成したら、今度は自分の好きなショートカットでこれが入力されるようにします。
「Preferences > Keymap」に進んで、虫眼鏡のところでmacro
と入力しましょう。下のように絞り込まれるはずです。
ここに先程作成した$
と->
があるのがわかります。あとはここに自分の好きなキーの組み合わせを設定することで、以後このキーを入力すれば$
と->
が入力できるようになります!
ちなみに僕は
$
→ctrl+s
->
→ctrl+.
という設定にしました。ちょっとですけど快適になった気がします!
他の皆様がどうしているのか気になったりします(むしろ気にしてないですかね。。。)
まとめ
- PHPStormのマクロを設定した
- マクロならコードスニペットとは違ったショートカットを作ることができる
explainshell.comはシェルの強力な助っ人
最近explainshell.comなるものを今更ながら知ることができたので紹介します!
概要
シェルコマンドを全部覚えるのって大変です。なんとなく覚えてても、オプションがどんな意味をもっていたかまではなかなか思い出せないです。おまけにシェルは強力なのでパイプ(|)でどんどん繋げていい感じにコマンドを書いていくことができます。そんな芸術ともいうコマンドを見ては、man
を見たりググったりして、分解しながらコマンドを理解していっているのはきっと僕だけじゃないはず。
そこで便利なのがこのexplainshell.comというサイトです!
使い方
使い方はいたって簡単で、インプットに知りたいコマンドを入力するだけ。
試しに最近覗いてみた下のk3sのインストールスクリプトから抜粋してみます。
lsof | sed -e 's/^[^0-9]*//g; s/ */\t/g' | grep -w 'k3s/data/[^/]*/bin/containerd-shim' | cut -f1 | sort -n -u
すごい!全部のコマンドに丁寧な説明がいい感じにつくんです!
そして「←→」で順番にコマンドの解説を表示していくことも可能。
とってもシンプルですけど、ブックマークしておけば困ったときにサクッと使えて重宝しそうです!
おまけ
おまけですが、僕の場合はAlfredにショートカットを作ってすぐに調べられるようにしてます。
参考までにWeb Searchの設定は下のようにしています。
そうすると下のようにすぐに調べることができます!
まとめ
- explainshell.comを試した
- シェルコマンド調べるときに便利
- Alfredなどツールと連携すればさらに強力!
Node.jsではてなの自分の記事一覧を取得する
はてなの自分の記事一覧をサクッと取得できるようにしたかったのですが、意外と
- Node.jsで書かれている
- 結果をJSONにする
ものが見当たらなかったので結局自分で作ることにしました!
ツールの流れ
はてなから自分の記事一覧を取得する流れは下のようになります。
- はてなのAtomPubを使う(認証はこの記事ではBasic認証を使います)
- エンドポイントをGETすると最新の記事が10件とれる
- 結果はXMLなのでJSONに変換する
- 2のレスポンスに次の10件のURLが記載されているので、全部取得終わるまで2, 3を繰り返す
はてなのAtomPub
はてなのAtomPubの仕様については以下Hatena Developer Centerに記載されています。
認証のところに書かれていますが、取得時に使える認証は
の3種類です。個人的にしか使わないし、簡単な方法でとりたいので今回はBasic認証を使うことにしました!
Basic認証に必要なユーザー名ははてなIDで、パスワードはAPIキーを使用します。
APIキーははてなブログの管理画面から設定>詳細設定に行った場所にあります!
下の方を見るとAtomPubの項目があり、そこにAPIキーがあるのでメモっておきましょう!
ブラウザからGETしてみる
それでは、試しに記事一覧をブラウザから取得してみます。
ドキュメントにも記載がありますが、以下のURLで取得できます。
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry
僕の場合はてなID=kenev
でブログID=kenfdev.hateblo.jp
なので、以下になります。
https://blog.hatena.ne.jp/kenev/kenfdev.hateblo.jp/atom/entry
これをブラウザに入れると、下のようにBasic認証が聞かれるはずです。
ここにユーザー名とAPIキーを入力すると、下のようなXMLが表示されます。
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app"> <link rel="first" href="https://blog.hatena.ne.jp/{はてなID}}/{ブログID}/atom/entry" /> <link rel="next" href="https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry?page=1377584217" /> <title>ブログタイトル</title> <link rel="alternate" href="http://{ブログID}/"/> <updated>2013-08-27T15:17:06+09:00</updated> <author> <name>{はてなID}</name> </author> <generator uri="http://blog.hatena.ne.jp/" version="100000000">Hatena::Blog</generator> <id>hatenablog://blog/2000000000000</id> <entry> <id>tag:blog.hatena.ne.jp,2013:blog-{はてなID}-20000000000000-3000000000000000</id> <link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/ ブログID}/atom/edit/2500000000"/> <link rel="alternate" type="text/html" href="http://{ブログID}/entry/2013/09/02/112823"/> <author><name>{はてなID}</name></author> <title>記事タイトル</title> <updated>2013-09-02T11:28:23+09:00</updated> <published>2013-09-02T11:28:23+09:00</published> <app:edited>2013-09-02T11:28:23+09:00</app:edited> <summary type="text"> 記事本文 リスト1 リスト2 内容 </summary> <content type="text/x-hatena-syntax"> ** 記事本文 - リスト1 - リスト2 内容 </content> <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#"> <div class="section"> <h4>記事本文</h4> <ul> <li>リスト1</li> <li>リスト2</li> </ul><p>内容</p> </div> </hatena:formatted-content> <app:control> <app:draft>no</app:draft> </app:control> </entry> <entry> ... </entry> ... </feed>
XMLが取得できそうだとわかったところで、Node.jsで実装してみます!
Node.jsで取得する
まずはソースを載せちゃってからポイントを解説します。
const axios = require('axios'); const parseString = require('xml2js').parseString; const hatenaAPIUser = '<USER_ID>'; const hatenaAPIPassword = '<API_KEY>'; const entryUrl = `https://blog.hatena.ne.jp/<USER_ID>/<BLOG_ID>/atom/entry`; const creds = `${hatenaAPIUser}:${hatenaAPIPassword}`; const encoded = Buffer.from(creds).toString('base64'); const authorizationHeader = `Basic ${encoded}`; /** * Promise based timeout * @param {number} msec wait milliseconds */ function wait(msec) { return new Promise(resolve => { setTimeout(() => { resolve(); }, msec); }); } /** * Promise based XML->JSON Parser * @param {object} data XML Data */ function parseStringPromise(data) { return new Promise((resolve, reject) => { parseString(data, (err, result) => { if (err) { reject(err); return; } resolve(result); }); }); } /** * Get all Hatena entries recursively * @param {string} url Entry URL */ async function getAllEntries(url) { console.log('fetching:', url); const entries = await axios.get(url, { headers: { Authorization: authorizationHeader, }, }); const { feed: { entry, link }, } = await parseStringPromise(entries.data); const next = link.find(l => l.$.rel === 'next'); const parsedEntries = entry .filter(e => { const { 'app:control': [ { 'app:draft': [isDraft], }, ], } = e; return !(isDraft === 'yes'); }) .map(e => { const categories = e.category ? e.category : []; return { id: e.id[0], title: e.title[0], url: e.link[1].$.href, published: e.published[0], publishedAt: new Date(e.published[0]).getTime(), updated: e.updated[0], updatedAt: new Date(e.updated[0]).getTime(), summary: e.summary[0]._, categories: categories.map(c => c.$.term), }; }); if (next) { // recursively fetch if 'next' exists const nextHref = next.$.href; await wait(500); const nextEntries = await getAllEntries(nextHref); return [...parsedEntries, ...nextEntries]; } else { return parsedEntries; } } getAllEntries(entryUrl).then(console.log);
ポイントとしては
- 外部リクエストにはaxiosを使い、BASIC認証用のHTTPヘッダーを作成している
- XMLからJSONにはxml2jsというライブラリを使っている
- 無限ループで負荷かけちゃうの怖いのでリクエスト間に500ミリ秒待つ
- 最終的なJSONの形は自分で決める
となります。順番に説明します!
外部リクエストにはaxiosを使い、BASIC認証用のHTTPヘッダーを作成している
最近axiosを使う機会が多かったので、なんとなく外部リクエストのライブラリはaxiosにしました。 ライブラリは割とどうでもいいのですが、ポイントはBASIC認証のHTTPヘッダーを作っている部分です。
const creds = `${hatenaAPIUser}:${hatenaAPIPassword}`; const encoded = Buffer.from(creds).toString('base64'); const authorizationHeader = `Basic ${encoded}`; ... const entries = await axios.get(url, { headers: { Authorization: authorizationHeader, }, }); ...
BASIC認証を実現するためにはHTTPヘッダーに Authorization
というものを作って、中身はBasic <ユーザー名:パスワードをbase64エンコードした値>
となります。
XMLからJSONにはxml2jsというライブラリを使っている
XMLからJSONにするために下のライブラリを使っています。
axiosで取得したXMLの中身をこのライブラリに通すことで、JSONを取得することができます。
const parseString = require('xml2js').parseString; /** * Promise based XML->JSON Parser * @param {object} data XML Data */ function parseStringPromise(data) { return new Promise((resolve, reject) => { parseString(data, (err, result) => { if (err) { reject(err); return; } resolve(result); }); }); } ... const { feed: { entry, link }, } = await parseStringPromise(entries.data); ...
個人的にCallbackがあまり好きじゃないので、Promiseにわざわざ変換したfunction
書いてますけど、これは別に必要ないです。Promiseにしてawait
したかっただけです。
無限ループで負荷かけちゃうの怖いのでリクエスト間に500ミリ秒待つ
コードがバグっていてはてなさんのサーバーに変な攻撃を起こしてしまうのが怖いので、リクエスト間に待ちを発生させるようにしてます。
/** * Promise based timeout * @param {number} msec wait milliseconds */ function wait(msec) { return new Promise(resolve => { setTimeout(() => { resolve(); }, msec); }); } ... if (next) { // recursively fetch if 'next' exists const nextHref = next.$.href; await wait(500); const nextEntries = await getAllEntries(nextHref); return [...parsedEntries, ...nextEntries]; } else { return parsedEntries; } ...
先程と同じく、setTimeout
をPromiseにしてawait
使いたかったのでわざわざfunction
作っています。あとは再帰呼び出しをする直前にawait wait(500)
と書いて500ミリ秒待つようにしています。
最終的なJSONの形は自分で決める
XMLからJSONに変換したものは、そのままではものすごく使いづらいので、適宜興味のある項目だけ抜き取って最終的な形に整形します。
... const categories = e.category ? e.category : []; return { id: e.id[0], title: e.title[0], url: e.link[1].$.href, published: e.published[0], publishedAt: new Date(e.published[0]).getTime(), updated: e.updated[0], updatedAt: new Date(e.updated[0]).getTime(), summary: e.summary[0]._, categories: categories.map(c => c.$.term), }; ...
こうすると、下のようなJSONになります。
{ "id": "xxxxxxxxxxxxxxxxxxx", "title": "AWS SAA再認定のために5日間頑張ったこと", "url": "https://kenfdev.hateblo.jp/entry/saa-recertification", "published": "2019-03-08T20:42:58+09:00", "publishedAt": 1552045378000, "updated": "2019-03-08T20:42:58+09:00", "updatedAt": 1552045378000, "summary": "昨日AWSソリューションアーキテクト・アソシエイト(以下SAA)に再認定されました。3月中に取得しないといけないというミッションがあったので慌てて先週申し込みました。試験会場の関係で試験日まで5日しか無かったのですが、合格できたので備忘録を残しておきます。 TL;DR(まとめ) …", "categories": ["AWS"] }
まとめ
以上ではてなの記事一覧の取得ができます!最後はfs
を使ったりしてファイルにJSONを保存すれば何か他の用途に使うことができるかと思います。
以下リポジトリにコードを格納していますので、興味がある方は使ってみてください!
「k3sとラズパイでフロントエンド開発に挑戦した話」を発表しました
今週は2つのイベントでLTをさせていただきました!ほぼ同じ内容で登壇したので、2日連続で参加された方には申し訳なかったのですが、少し内容を変える努力はしました。。。
発表資料
ストーリー
フロントエンジニアでラズパイ持っててkubernetesに興味があったら、とりあえずラズパイ上にNuxt.js乗せたくなりますよね!?
ということで、今回はNuxt.jsをラズパイに入れたk3s上にデプロイすることに挑戦したときの話をしました!ポイントとしては下のとおり!
- arm32v7/nodeの
alpine
だとnpm install
でコケる - なのでARMのイメージで
alpine
はあきらめる docker create manifest
でマルチアーキイメージを作れる(ARMとx86_64で同じイメージ名!)- Dockerfileの
FROM
にARG
を使うとイメージレイヤのキャッシュが効かない疑惑がある
ヘルプをもらえました!
node
のイメージを素で使ってましたけど、 node-slim
にしては? というアドバイスがいただけました!
build-argsとキャッシュ問題はこれじゃないかというアドバイスをいただきました!https://t.co/yUkdVBH7YV #cnjp
— Ken Fukuyama (@kenfdev) June 7, 2019
半分以下には落とせました!けれど2桁までは落としたい気持ちがあります。。。
ARG
使うとキャッシュは効かない!? についてのアドバイスもいただけました!
arm32v7/nodeの `slim` を使えばいいのではというアドバイスも!試します!
— Ken Fukuyama (@kenfdev) June 7, 2019
#cnjp pic.twitter.com/oA57vHNqfH
上の公式ドキュメント読みましたけど、毎回同じ値にしてるのにキャッシュ効かないんですよねー。これは引き続き調査 or Dockerfileを分ければいいのかなと思ったり。
おまけ
こういうコメントがもらえてうれしいです!アプリ寄りな話を増やしたい気持ちはあるんですよね。
@kenfdevさんの、k3s on ラズパイに作ったフロントを乗っけて、フロント開発したという話。フロントもやりたい、kubeも学びたい人に良さそうである(^ω^)#cnjp
— おかもん@子育てしながら系エンジニア(・ω・) (@mojogeek666) June 7, 2019
なかなか時間を作るのにも苦労していますが、今後もフロントエンドとコンテナをからめていきたいです!
カックさん(@kakakakakku)さんのメンタリングを卒業しました
3月から5月の3ヶ月間に渡るカックさん(@kakakakakku)のブログメンタリングを卒業しました!
ここに卒業記事を残し、今後についても書き残しておきたいと思います。
メンタリングをなぜ受けたのか
僕はアウトプットすること自体は好きで、ブログを書いたりイベントに登壇したりは不定期にしていました。自分でも認識していた問題はアウトプットの習慣化がまるでできていなかった点です。その点を改善すべく、メンタリングを受ける決意をしました。
アウトプットの習慣化がなぜできないのか
「なぜアウトプットの習慣化ができないのですか?」と聞かれたら多くの人が「忙しくて時間が無い」と答えるかと思います。
今、「うんうん」と頷いた方、ぜひぜひ下の記事を読んでいただきたいです。これだけでもマインドセットが変わると思います!
僕も「時間が無い」と思っていた中の一人ですが、「時間が無い」と定義していたのは自分です。誰かに強制的に自分の時間を奪われていたわけではありません(そういう人もいるかもしれませんが…)。時間の使い方をコントロールできていなかったのが大きな原因だと気づくことができました。
3ヶ月の実績
3ヶ月の実績は下のとおりです。
項目 | 3月初 | 5月末 |
---|---|---|
Twitter Follower | 393 | 410 |
はてブ | 0 | 12 |
読者数 | 0 | 31 |
メンタリングが始まって結構すぐにCloud Native Kansaiという大きめなイベントでLT登壇することができたので、PVがすごく上がっています。
そのときの記事がこちら。
けれど、この波に乗ることができずにいっきに低迷!そこからOPAネタを書いていきながら、最後は書評に挑戦して徐々にPVを上げていくことができたかなと思っています(まだまだですが)。
驚くような結果にはならなかったので反省していますが、今後も継続して精進していきます!
課題
卒業とともにカックさんに最後のフィードバックをもらいました。中でも特に僕が今後も改善していきたい点については「満足できるクオリティに達するまでの距離を遠くに置きがち」なところです。週1のアウトプットを出していくことはできるようになったのですが、1アウトプットするまでに要している時間が多すぎると感じています。
いつもどうしても
- まとまりが悪い
- ストーリー性が見えにくい
- もっと違う観点を取り入れたほうがいいのでは
- などなどetc.
と考えてしまって、書いては消して、書いては消してを続けてしまいます。「小さく考える力」が圧倒的に足りていないのを痛感しています。 カックさんにも薦められた「小さな習慣」を読み、「常にアジャイルに、小さく考えられるように」改善し続けたいと思っています!
- 作者: スティーヴン・ガイズ,田口未和
- 出版社/メーカー: ダイヤモンド社
- 発売日: 2017/04/27
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る
メンター、カックさんについて
カックさんは本当にすごい方です。そのすごさは他のメンティの卒業エントリを見てもわかると思います。今後もエントリは増えていくと思うので、下のようにググったりTwitter検索した内容を確認すると勉強になります。
すごい点は数え切れないほどあるのですが、僕からも1点共有しておきたいです。
それは「やってみせる・やらせてみる・フィードバックする」のサイクルが本当にうまく回っていると感じた点です。
この記事を書いている最中にもカックさんからこんなツイートが流れています。
普通に envsubst のことを知らなかったから書いたんだけど,Linux コマンド1個で記事が書けるんだよ!というネタ粒度の観点をブログメンティに伝える目的もあったのだ😀 / Shell で「テンプレートエンジン」のような仕組みを実現できる envsubst コマンド - kakakakakku blog https://t.co/LuKL3Vriz1
— カック@ブロガー / k9u (@kakakakakku) 2019年6月3日
カックさん自身、かなり多忙なはずなのですが、毎週必ずアウトプットをしています。メンターであるカックさんがやってみせてから、メンティにもやらせてみる。そして、やらせてみた後は必ずフィードバックがもらえます。フィードバックは「よかった」「悪かった」なんてシンプルなフィードバックじゃなくて、かなり濃厚で、親身なフィードバックがいただけます。すごいのは、それらのフィードバックや他の通常な会話も合わせて、メンティの僕が精神的に負荷を感じることが無い点です。「がんばろう」という気持ちに素直になりますし、「しんどい」ときにはどう乗り越えていくことができるか相談に乗ってくれます。メンターの鏡です!自分自身で最高のメンターによるメンタリングを経験することができたので、僕もこうなれるように、この点についても日々精進していきたいです。
今後について
このメンタリングを通して毎週最低1記事を書くということが習慣化できたので、自分は成長したと感じています。メンタリング中に初挑戦した書評ともちょうどフィットするのですが、メンタリングを受けたことで、セルフマネジメントのレベルが上がって、ブログを書くことの習慣化が可能になったのかなと思います。
今後3ヶ月の目標としては
- 週1アウトプットを続ける
- ホッテントリな記事を書く
- 英語の記事も書く(https://medium.com/@kenfdev)
としたいです。
習慣化したのを継続すること(当たり前ですが!)、質の高い記事が書けるようになること。そして、英語のアウトプットも増やしていけたらと思います!(世界は広い!)
またちょっと長い目な記事になってしまいましたが、以上で卒業報告とさせていただきます!
カックさん(@kakakakakku)、本当にありがとうございました!!!