社内向けCLIツールのご紹介

2022/12/15

目次

この記事は207 Advent Calendar 2022 16日目 の記事です。

はじめに

忘年会から無事に帰還したゲインです。皆さん飲んでますか?

今回は僕が勝手に作っている便利cliについて簡単ににご紹介です。

概要

207のインフラは基本的にECS on Fargateを使用して構築されています。

単発で終わるコマンドを動かすには aws cliを使用してタスクを起動する必要がありますが、オプションの指定等が複雑です。

copilot cliecspresso等のサードパーティーのツールを導入もタスクを動かすためだけに導入するのは少々大げさです。

またDBとしてAurora Postgresを使っており,Private Subnetにある都合上ローカルからDBへの接続はできませんが、開発時には好きなクライアントを使用してDBの中を見たいという要求もあるでしょう。

そのためにsession managerを利用してport-forwardを行い接続することも可能となっていますが、やはり接続のためのオプションを指定するのは煩雑です。

そこで社内向けにGoでcli toolを作成し、これらの手順を簡略化しました

機能

ベースの機能として、selfupdateを提供しています。

https://github.com/inconshreveable/go-update というライブラリを使用しバージョンが上がるたびに自動で更新されるようになっています。

selfupdate

毎実行時にs3上に置いてある最新バージョンが書いてあるテキストをダウンロードし、バイナリに埋め込まれたバージョンと比較します。

その際、差分がある場合は最新のcliをバケットからダウンロードし更新を行います。

このときの権限はそれぞれの開発者の権限を使用します。

すでにログインしている場合はその権限を使い、未ログインの場合はCLIの中でaws ssoを行います。

s3側ではOrganization内のユーザーからのみダウンロードできるようにBucket policyを設定しています。

現状のロジックではバージョンの差分がある場合は更新していますが、そこのロジックは自由に変えられるのでマイナーバージョンのみ自動デップデートやメジャーバージョンのアップデートは対話式にするなど柔軟に変更することが可能です。

バージョン用のテキストファイルとバイナリはGitHub ActionsでBuildするタイミングでs3へ配置しています。

runtask

runtask

既存のタスク定義を対話的に選択し、任意のコマンドを打つためのサブコマンドです。

引数として実行したいコマンドを入力して実行します。

SecurityGroupやサブネット等は社内の事情に合わせて自動で選択しているため、特に設定を行う必要はありません。

実行後はNewRelicのログやコンソール上からタスクの状態を確認してもらいます。

dbconnect

dbconnect

接続できる環境名を選択し、接続可能なDatabaseを探し出し、そのインスタンスへport-forwardを行うコマンドを実行します。

やってることは aws ssm start-sessionのWrapperなので特に語ることはありません。

現状ではDBへの接続のみが可能ですが、今後Redisや任意のインスタンスへのport-forwardを実現できてもいいかもしれないですね。

まとめ

僕が半分遊びで作っているcliのご紹介でした。

今後も随時アップデートしていきたい所存です!

207のインフラ周りをご紹介

2022/12/01

この記事は207 Advent Calendar 2022 2日目 の記事です。

はじめに

207で主にインフラ周りを見ているゲインです。

今回は副業時代から基本的に一人で見ているインフラをご紹介しようと思います。

インフラ構成

アプリケーション

基本的にインフラはAWS上に構築されています。

一部画像解析やGeocoding, アプリの分析データなどでGCPおよびFirebaseを使用している形となっています。

AWS上の主なアーキテクチャはこちらになります。 207 app system diagram

基本的にアプリケーションは全てコンテナ化しており、Ruby(Rails)とnode.js(Next.js)をFargate上で動かしています。

主なトラフィックとしてはアプリからRailsのAPIへのリクエストとなっています。 配達員向けのアプリを提供している都合上早朝の時間帯は書き込みのリクエストが多く重い傾向です。 昼間は読み込みのトラフィックが多い状態で夜間はほぼほぼトラフィックがないような状態です。 そのため、AutoScalingをSchedulingしており重い時間帯のみコンテナを増やしています。

Next.jsのSSRでは対したトラフィックもないのでinternalな通信をせずに一旦外部に出てRailsのAPIを叩く構成になっています。 将来的にユーザーが増えた場合はinternalなLBを挟むのかコンテナ間通信にするのか考慮しますが、当分はシンプルな状態で運用していきます。

その他管理画面やWebの機能が存在しており,静的なファイルはCloudFrontから吐き出しています。

デプロイはCodeDeployのB/Gデプロイを活用しています。 現状はECRへのPushを起点にCodePipelineを起動しているのですが、いずれCodeDeploy単体でデプロイを行おうと密かに画策中です。

運用

サーバー側のログ、メトリクスやAPMなどは全てNewRelicに集約しています。 バックエンド側ではそれぞれのアプリケーションのSidecarとしてNewRelicのコンテナを起動し、ログやメトリクスの送信を行っています。

アプリ側ではエラーログをSentryに送信し、日々のエラーログ等を観測しています。 アプリはReactNativeを使用しており、NewRelicのReactNative Agentが最近GAばかりなのでまだきちんと導入まで出来ていません。 いずれアプリ側サーバー側をNewRelicで一気通貫で見れるとよいのかなと考えています。

インタビュー記事も上がっているので良かったら見てみてください。

https://newrelic.com/jp/customers/207

データ分析

ざっくりはこちらとなっています。

207 system diagram-datamask architecture

現状データはAuroraからs3にEmbulkを使用してparquet形式で吐き出してRedash経由でAthenaによるデータ分析を行っています。

EmbulkのコンテナをそれぞれStepFunctionで起動しています。 それぞれのコンテナは1テーブルのみをETLすることだけに注力するようにStepFunctionからcommandのoverrideを行っています。 StepFunctionのそれぞれのテーブル毎の失敗通知はAPI Gatewayの機能を使ってStepFunctionからSlackへ通知を行っています。 あまりLambdaを乱立させるのが好きではないのでこの方法は気に入っています。

Schemaに関してはこちらのプラグイン (embulk-output-s3_parquet)を使用してGlue Catalogで日々更新されています。

出力されたデータはECS on EC2上に構築したRedashで閲覧することが可能です。

今後の課題

アプリケーションに関してかなり雑なスペックおよび台数の決め方をしているため、これらを最適化するために負荷試験を行っていく必要があります。 定期的なライブラリのアップデートも疎かになっている部分があるので、定期的に検知と対応を行う必要もあるでしょう。

運用に関してはSLOをきちんと定義しきれておらず、本当にユーザー影響を感知するような仕組みを作りきれていないというのが正直なところです。

またオンコールの体制も組みきれていないので、ユーザー影響を最小化するための活動はまだまだといったところですね。

データ分析に関してはかなり適当にデータをマスクして吐き出しているだけとなっているため、今後ゴリゴリと分析を行っていく用途には耐えられないことがすでに判明しています。 どのデータは誰が見れてよいのか、どこにデータを格納していくかを今後検討してく必要がありそうです。

まとめ

ざっくりと207のインフラをご紹介しました。 まだまだインフラ周りでの課題はたくさんあるので、メンバーと協力して改善していきたいです。 今後とも応援よろしくお願いします!

207株式会社に入社します

2022/07/10

目次

tl;dr

FROM: 合同会社DMM.com

TO: 207株式会社

今まで何やってたの

2019年に新卒で合同会社DMM.comに入社しました。

主にAWSを触る感じでデータ移行したりk8s運用したり一生terraform importのコマンド打ったりCI/CD触ったりDatadogいじったりしてました。

入社するまで見えていなかった世界を幅広く知ることができ、僕のキャリア形成の礎となったことは間違いありません。

また偉大な先輩方や技術に明るい同期、技術に貪欲な後輩たちと出会えたことは何より大切にしていきたい点です。お世話になりました!

これから何やるの

207株式会社に一人目のSREとして入社します。

実はすでに副業で1年半程度関わりがあり、インフラ周辺はほぼ僕が作っているのでとんでもない負債があるというわけではありません。

なので一歩進んでSRE的な文化をインストールしたり、CI/CDや細かなセキュリティ部分のアップデートをしていこうかなと思っています。

その辺は後ほど会社のBlogにでも書こうかなと思っているので楽しみにしていてください。

本業でも副業としてもあくまでお手伝いの形で仕事をすることが多かったのですが、正社員になって特定プロダクトへのコミットをするというのは今までできてなかった部分なのでチャレンジしていきたいと思います。

さいごに

207株式会社に興味がある方、僕とお酒を飲みたい方はよかったら声をかけてください。

また副業が本業になる関係で副業はなくなるので、ゆるく募集しておきます。

2020年の振り返りと202l年の抱負

2020/12/25

目次

はじめに

今年全然Blogかけてなかったゲインです。

仕事納めたので今年の振り返りと来年のTODOリストを残して置きます。

振り返り

今年はコロナでひたすらリモートワークの年でした。

幸い会社は3月くらいのタイミングでリモートワークを許可してくれたり、そのまま現在まで通勤から開放されてありがたい毎日です。

仕事内容としては、昨年からスタートしたプロジェクトも3月のタイミングで無事に終えることが出来ました。

新卒で部に配属されてから初めてほぼ一人で担当したプロジェクトだったので現在の無事にシステムが動いてくれていて嬉しく思います。

その時に現在の師匠から様々な知見を吸収することも出来て、ガッツリ経験値を稼げたなと思います。

また4月には新卒に向けてAWSの初級講習を担当しました。

一年前の自分が学んだときのことを思い出しながら2日担当しましたが今年の新卒にうまく伝わっていれば良いなと感じます。

9月くらいまではちょこまかとしたAWS周りのタスクを消化する毎日でした。

9月になり、バチクソに強い人達が集まった部に異動することになりEKSの運用とそれに乗るAPIの開発を行うようになりました。

それまでは、k8sをなんとなく追っかけたりraspi clusterを作ったりしてましたが やはり本業でガッツリ触るのは大違いですね。

未だに使ってるコンポーネント郡(Istioとか)をきちんと把握できていないので、来年は自分で触って理解していきつつクラスターの安定稼働に必要なことを積み重ねていきたいです。

来年のTODOリスト

抱負は文章書くのがだるいので諦めました。 誰か来年の今頃にこのTODOリストをTwitterかどこかで投げつけてくれると幸いです。

生活関係

  • カレー作り極める
  • 映える自宅飯を作る
  • 魚さばいてぱーちーする
  • 和食のバリエーション増やす
  • ちょっとしたサラダとか小鉢みたいなのを作れるようになる
  • 体重75kg到達
  • 筋トレ継続
  • 彼女

技術関係

  • 本業でバリュー出す
  • 副業込みで年収700万
  • k8s関連の副業やってみる
  • CKA,CKAD,SAP取得
  • IstioとEnvoyとApp Mesh辺りをしっかり触る
  • 自作CRI雑でいいから作る
  • Rustを学ぶ
  • CDK触れるぐらいにはTypeScript
  • 自作OSの本に手を出し始める

おわりに

大変な年でしたがなんとか生きていてよかったです。

来年もよろしくお願いします。

2019年の振り返りと2020年の抱負

2019/12/27

目次

はじめに

仕事を納めたゲインです。

今年は1年を通して色々あったので振り返りたいと思います。

振り返り

卒業

今年の3月に長岡技術科学大学大学院を卒業しました。

卒業に関しては色々ありました。

正直思い出したくないので一言言えるのは学生相談室の人は相談内容を平気でばらすんですね。以上。

入社

4月には無事DMM.comに入社することが出来ました。

新しいCTOになって 初の新卒ということで、 新卒研修も大幅にアップデートされており、非常にラッキーだったなと感じています。

オンプレの話からAWS,フロントエンド(React), バックエンド(Go)、アプリ(kotlinとswift)のWeb開発。

知識のinputとして輪読や負荷試験、セキュリティなどなどの非常に抱負な内容を一流のエンジニアから学べたのは中々他の研修ではないんじゃないでしょうか。

来年度に向けて講師陣が気合を入れているので今後入ってくる人たちはもっと良い研修を受けられると思いますし、僕も協力できるところはお手伝いしていきたいです。

AWS

今年触った内容の中でも、AWSに関しては手厚い研修内容や非常に優秀な先輩がいた事でとても理解が進んだ内容だと思います。

書いたブログ がちょっとバズったりしたおかげで、 AWS公式のトレーニング採用事例に乗ったり、新卒の見ながらre:Invent 2019に参加して 海外のエンジニアと交流する機会をいただけたりと学ぶには最高の環境でした。

知識の面でも、入社当時は以下のような画像にあるアーキテクチャでシステムが動いていると思っていました。

old_3tier

しかし、様々学ぶにつれて本当は以下の画像なアーキテクチャが必要だということがわかり、 過去の自分を恥じたのと同時に、 これがわかるようになったという成長も感じました。 3tier

その過程でSAA取ったりDVA取ったりもしました。 ラスベガスでSysOps落ちたのはすごく残念ですが。

仕事

様々な研修を経て7月にはSRE部に配属されました。

まぁ色々とありましたが現状はとてもいい環境に身を置かせてもらっているなと感じています。

特にメンター的な役割としている方と出会えたのも非常にラッキーで、 アルバイト時代も今もメンターの人に非常に恵まれている人生だなと感じます。

ただやはり仕事に対して自分一人で価値を出し切れていないなと思うことが多々あります。

クラウドのアーキテクチャもそうですし、そもそもDNSの仕組みがわかっていなかったり、 ネットワークの基本的な部分がわかっていなかったりというのが露呈してきました。

今だから思うのはちゃんと学生時代に勉強しておけばよかったということです。 まぁそれが出来たら苦労はしないし、周りが言っても無駄なんですけどね。

そういった基本的な知識がなくてもAWSは触れましたが、 クラウドにおんぶにだっこな状況だと、それに振り回されたりすることもあると感じました。

なのでクラウドの奥がどうなっているかをちゃんと考えれるようになりたいというのが最近の目標です。

あとまだまだ新卒のラベルが剥がれてないなとも感じています。

一度本番でやらかしちゃったことがあるのですが、そのときも「まぁ新卒だから仕方ないよ」みたいな慰めを受けました。 それ自体はありがたいのですが、仕事の結果に新卒もくそも無いのでそれを起こしてしまった事と、 それを先輩に言わせてしまったことがとても悔しいです。

新卒だから仕方ない、新卒の割にはよくやっているのようなことを言わせずに個人としてきちんと成果を出すのとそれを周りに見せていくことを今後は心がけたいです。

2020の抱負

来年は20卒も入ってきて新卒だからというのは通じなくなります。

なのできちんと仕事の成果に対して責任を持つこと、またそれを実現できるように技術や知識を身に着けていくことを重点的にやっていきたいです。

具体的にはk8sの知識の習得や、 コンテナ全般の知識の補充。 Linuxのプロセスやメモリ管理の話やネットワークの話などCSに近いような部分の話。 GoのアーキテクチャやIaCのベストプラクティスなどなど。 SREとして監視やSLA、SLO周りの策定に関する知識。 これらの主にサーバーに関連する部分の知識を身に着けていきたいと思います。

またその過程でAWSのSolution Architect Professionalと専門知識からなにか資格も取りたいなと思います。

あとは個人としての価値を高めていくために副業等も1件はやってみたいと思います。

現状では会社の名前と新卒ラベルで下駄を履かされているのでそれをとっとと捨てたいです。

まとめ

いまいちまとまらん話でもありますが、今後はもっとやっていきってことです。

主にクラウド、サーバーのあたりを重点的に攻めて、社内の色んなプロジェクトを救える人材になりたいと思います。

最後に素晴らしい環境を与えてくれた会社と今まで出会った皆さんに感謝します。

来年もよろしくおねがいします。

AWS Developer Associateを取得した話

2019/10/19

目次

はじめに

どうもAWS Developer Associateのゲインです。

タイトル通り2019/10/19にAWS Developer Associate(以下DVA)を取得してきたので雑記を残します。

対象読者

  • DVAを受けるか迷っているエンジニア
  • どんな対策したか気になるエンジニア

DVAとは

今回受験した資格はAWS 認定デベロッパー – アソシエイトと呼ばれる、AWSに関する技術的スキルと専門知識を証明するための資格です。

具体的には以下のような知識が問われます。

  • あるケースではどのAPI呼び出しを使うべきか?
  • これを実現するのに必要な手順は?

そのためSAAと比べサービス全般知識ではなく、実際の開発に必要な細かい知識が求められる試験だったと感じました。

勉強内容

さて今回合格に至るまでどんな勉強をしたかをざっくりご紹介します。

今回僕が取り組んだのは以下のコンテンツです。

  • AWS公式の模試
  • Udemyのコースをつまみ食い
  • DynamoDBの勉強会に参加
  • 実際に触ったり、APIリファレンス読んだり

AWS公式の模試

とりあえず現在の知識を確かめるために模試を受けました。

以前SAAを取得していたので、模試は無料で受けることが可能でした。

模試の結果、総合スコアは70%で少し勉強が必要だったなという印象でした。

Udemy

Udemyで今回受講したコースはAWS Certified Developer - Associate 2019です。

こちらは定価だと18,000円もするのですがまぁUdemyはしょっちゅう割引をやっているので実際は1,800円で買った気がします。

こちらのコースではそれぞれのサービスに関するビデオ講義があり、最期には小テストを受けることができます。

また2つほど模試のような形で問題形式が付随しており、自分の知識を確かめることが可能でした。

やはり問題形式では、わかってるつもりの知識も間違えてしまうことがあって確認には非常に良かったです。

DynamoDBの勉強会

というツイートに対して、会社の先輩が申し込みをしてなんやかんやで勉強会が開催されることに。

これに関してはそのうち先輩が素晴らしい体験記事を上げてくれるはずなのでお待ち下さい。

試験後の感想ですがDynamoDBの出題はかなり多いなと感じたので、概念的な部分や単語、ユースケースやベストプラクティスを教えていただいてかなり試験ではきちんと答えることが出来たと感じました。

当日優しく教えてくださった@oranieさんのおかげで合格したと言っても過言ではありません。ありがとうございました。

実際に触ったり、調べたり

それ以外として、あたりまえですが実際にAWSを触ってみました。

特にElasticBeanstalkやEC2は業務では使わない部分だったので自分で触ってみて感触を確かめてみました。

また模試やUdemyのテストでは、あるユースケースに対してどのAPIを使うべきかと言った問題も沢山あったので、 適当にS3、EC2、IAM周りのリファレンスを読みました。

特にSTS周りはわかってない知識が多く新鮮でしたね。

結果報告

既に結論は書いてありますが無事に合格することができました。

スコアは831とそこそこの点数を取れたのではないでしょうか。

aws-dva

スキル的な話として普段適当に使っているIAM周りや基本的なEC2周りの知識を獲得できたのが良かったなと感じます。

普段はFargate等を触ることが多いので、逆に基本的な部分が疎かになっていたなと実感しました。

これでSAAと合わせて二冠なのでSysOpsを取らない理由がなくなりましたね。

がんばります。

まとめ

  • DVAは実際の開発時に必要な知識が問われる
  • 資格取得を通じてAWSにもっと詳しくなれる
  • AWSの新卒研修があって、社内に詳しい人がたくさんいて、月1万までAWSとGCPを使えるTechな会社があるらしい

CircleCIでRubyのvendorをWorkflow間で共有した時の失敗談

2019/10/10

目次

はじめに

どうもお仕事でAWS触ることが多くなっているゲインです。

最近Rubyを動かすためのAWS LambdaをTerraform + CircleCIでDeployするという構成でちょっとハマったので失敗談と解決方法の一例を示します。

TL;DR

  • それぞれのコンテナでユーザーおよび$HOMEが違う
  • そのためCircleCIの working_directory~ を使うと異なるパスになることがある
  • 結果的に restore_cache が別のパスに保存されてしまうことがある

前提条件

  • CircleCI上でTerraformが実行されて反映される環境である。

ハマったこと

プロジェクトの都合でRubyを使ってLambdaを動かすことになりました。

CircleCI上でのCI/CD環境が既にあったのでWorkflowを使って以下ような事を考えて、フローを追加しました。

  • Terraformは既に hashicorp/terraform で動いているでそのままにする
  • Rubyのvendorは別のjobでコンテナイメージを変更して動かす

それを実際に反映したのが以下のconfig.ymlです。

# config.yml
version: 2
jobs:
  build: # Terraform apply
    docker:
      - image: hashicorp/terraform:0.11.14
    working_directory: ~/terraform
    environment:
      AWS_DEFAULT_REGION: ap-northeast-1
    steps:
      - checkout
      - restore_cache:
          key: source-v1-{{ .Branch }}-{{ .Revision }}-{{ checksum "lambda/src/myfunction/Gemfile.lock" }}
      - run:
          command: |
            terraform init -backend-config="bucket=${BUCKET_NAME}" -backend-config="key=terraform.tfstate" -backend-config="region=${REGION}"
            terraform apply -var-file=stg.tfvars 
 
  bundle_install: # bundle install for Lambda function
    docker:
      - image: circleci/ruby:2.5.0
    working_directory: ~/terraform
    steps:
      - checkout
      - run:
          name: bundle install
          command: |
            cd modules/sfn/lambda/src/myfunction
            bundle install --path vendor/bundle
      - save_cache:
          key: source-v1-{{ .Branch }}-{{ .Revision }}-{{ checksum "lambda/src/myfunction/Gemfile.lock" }}
          paths:
            - "lambda/src/myfunction/vendor"
 
 
workflows:
  version: 2
  deploy:
    jobs:
      - bundle_install
      - build:
          requires:
            - bundle_install

bundle install を実行するジョブでは circleci/ruby:2.5.0 を使います。

実行した後のvendorはCircleCIの save_cache を使ってキャッシュしました。

そのキャッシュしたvendorはterraform applyを行うための hashicorp/terraform:0.11.14restore_cache しました。

さてこれで bundle install でvendorが作成され、TerraformでそれらがzipにまとめられLambdaで使えるようになりましたとさ。

...とはなりませんでした。

バッチ処理のStep Function内で使われるLambdaだったので、朝起きるとCloudWatch Logsにはこんなエラーが。

Traceback (most recent call last):
main.rb:1:in `<main>': undefined local variable or method `GEM_NAME' for main:Object (NameError)

なぜかLambdaにはvendorがなく、使いたかったgemを使えないという問題でした。

原因

今回使っていた circleci/ruby:2.5.0hashicorm/terraform:0.11.14 ではユーザが異なっていました。

実際に手元でdockerコンテナ内に入って確認してみましょう。

まずは hashicorp/terraform のコンテナに入ってみます。

# hashicorp/terraform
docker run -it --entrypoint=ash hashicorp/terraform:0.11.14

# コンテナ内
circleci@3d233757b0cc:/$ whoami
root
circleci@3d233757b0cc:/$ echo $HOME
/root

ユーザーは circleci$HOME/root となりました。

次に circleci/ruby:2.5.0 に入ってみます。

# circleci:ruby
docker run -it circleci/ruby:2.5.0 /bin/bash

# コンテナ内
circleci@3d233757b0cc:/$ whoami
circleci

circleci@3d233757b0cc:/$ echo $HOME
/home/circleci

ユーザーは circleci$HOME/home/circleci となりました。

つまりは以下のようなことでした。

  • circleci/rubybundle install したときは /home/circleci/workdir/lambda/src/vendor のパスでキャッシュされる
  • hashicorp/terraform のジョブでは /root/workdir/lambda/src/vendor のパスを期待している
  • 実際には /home/circleci/workdir/lambda/src/vendor に実際にはキャッシュが復元される

同様の事例として、CircleCIのサポートにも似たような話があります。

解決策

上記のCircleCIのサポートページには以下の記述があります。

可能なら、複数のジョブ間で同じイメージを使用してください。 それが難しい場合は、2 つのイメージ間で共通の場所に作業ディレクトリを設定してください (/tmp が使用できることがあります)。 また、キャッシュを保存する前に、ファイルのアクセス許可を変更または設定する必要があります。

そのため bundle install のパスを /tmp 配下にして、そこから取り出すという手段をとってみました。

configは以下のようになります。

# config.yml
version: 2
jobs:
  build: # Terraform apply
    docker:
      - image: hashicorp/terraform:0.11.14
    working_directory: ~/terraform
    environment:
      AWS_DEFAULT_REGION: ap-northeast-1
    steps:
      - checkout
      - restore_cache:
          key: source-v1-{{ .Branch }}-{{ .Revision }}-{{ checksum "lambda/src/myfunction/Gemfile.lock" }}
					
      ## ここを追加!!
      - run:
          name: Move tmp to app dir from /tmp
          command: mv /tmp/vendor lambda/src/vendor
 
      - run:
          command: |
            terraform init -backend-config="bucket=${BUCKET_NAME}" -backend-config="key=terraform.tfstate" -backend-config="region=${REGION}"
            terraform apply -var-file=stg.tfvars 
 
  bundle_install: # bundle install for Lambda function
    docker:
      - image: circleci/ruby:2.5.0
    working_directory: ~/terraform
    steps:
      - checkout
      - run:
          name: bundle install
          command: |
            cd modules/sfn/lambda/src/myfunction
            ## ここを修正!!
            bundle install --path /tmp/vendor/bundle
      - save_cache:
          key: source-v1-{{ .Branch }}-{{ .Revision }}-{{ checksum "lambda/src/myfunction/Gemfile.lock" }}
          paths:
            - "lambda/src/myfunction/vendor"
 
 
workflows:
  version: 2
  deploy:
    jobs:
      - bundle_install
      - build:
          requires:
            - bundle_install

ただこちらの解決策も本来はいまいちなはずなので、実際には次のような対応を取ると良いでしょう。

  • circleci/rubyをterraformのジョブでも使う
  • terraform自体はgoで作らているのでBuild済みのものを公式から取得して使う

まとめ

CircleCIのworkflow間で外部ライブラリを save_cache で共有するときのハマったところと解決策を紹介しました。

ここまで想像力が働かずにバッチが失敗してしまったので今後は気をつけたいですね。

TerraformでLambdaを触るのは少し辛くなってきたのでなにかベストプラクティスが欲しいところです。

AWSさんはGlueのCralwerをLambda無しでイベントから直で動かせるようにしてください。

AWSをちょっと理解するまでの4ヶ月

2019/08/11

目次

はじめに

どうもいつのまにか社会人になっていたゲインです。

新卒研修や資格取得を通してAWSの事がだんだんと分かってきたので、 どうして今までAWSがわからなかったのか、どうして分かるようになったのかを残します。

よくあるこれをやったら絶対合格とかそういうのは出てきませんのでご了承ください。

対象読者

  • AWSを触りたい初心者
  • AWSを触っているがイマイチ掴みきれていない初学者
  • 初心者がどこで詰まっているのかよくわからないリードエンジニア
  • 来年の新人研修をどうすべきか悩んでいる人事および担当エンジニア

AWSが何もわからん時

学生時代はバイトでGoのサーバーサイドのAPIを書いており、 そこでもAWSを使っていました。

今なら何となくその時の構成も分かるのですが、 当時はGoを書くことで精一杯だったので、AWSの事までは追いついていない状態でした。

当時分かっていたのは以下の項目です。

  • アプリケーションはEC2で動いているらしい
  • どっかにnginxが動いているらしい
  • S3ってところに画像とかをおいているらしい
  • なんかAurora?っていうDBを使ってるらしい

今考えればまぁよくあるWeb3層アーキテクチャですね。

2019年3月くらいまでの私はそういう人間でした。

AWSをちょっと理解した

なんやかんやでバイト先を卒業し、 新社会人として東京のTechな企業で務めることになりました。

そこでは多種多様な技術を3か月でツメツメに学ぶという素晴らしいカリキュラム(本当に言葉通りの意味です)が組まれており、 その中にAWSの研修がありました。

4日間かけて実際にAWSの方から研修用コンテンツである AWS Technical Essentials 2Architecting on AWSのトレーニングを実施していただきました。

その中では以下のようなことを学びました。

  • AWSの基本的なサービスの紹介
  • 仮想的なクラウド移行のシナリオをもとにした設計
  • AWSを利用したWeb3層アーキテクチャや
  • 機械学習SaaSを利用したアプリケーションの構築・デプロイ

特に良かったのが毎日ペアやグループで設計についてお絵描きや議論する部分があり、 このサービスはどこに図示すればよいのかをきちんと身につけるられるプログラムとなっていました。

例えば一般的な3層アーキテクチャをMulti-AZで図示してくださいという課題では以下の部分で悩みました。

  • Multi-AZ間でのSecurity Group
  • ELBの配置
  • AZサービス、Regionサービス、Globalサービスなのか

それらの悩みを実際にAWSの講師の方に質問しながら課題に取り組むことが出来たため、 今まで自分がわかってない部分をきちんと実感し、それを学習することが出来ました。

そのおかげもあって18人にいる同期はAWSの詳しく、 研修のチーム開発ではチームメンバー全員が設計について議論できるレベルで非常に良い体験でした。

凄く良い研修だったので来年の新人研修を設計している方は考慮してみると良いでしょう。

AWSをさらにちょっと理解した

仲の良い先輩社員さんが同期に対して資格取れよ〜とだる絡みしているところを、 後ろで聞いていたら私も巻き添えを食らって、雑に受けることになりました。(この時受験日の10日前)

受験した資格はAWS 認定ソリューションアーキテクト - アソシエイトと呼ばれる、 AWS上での安全で堅牢かつ最適なコストでシステム設計可能な能力を証明するための資格です。

実はこちらの出題範囲は研修で行った Architecting on AWS が主で、少し足りないところを補足したら合格できるかなという感じでした。

自分がどこまで理解しているかを確認するために模擬試験をまずは受けました。

結果としては正答率70%でまぁ大体はわかるけど…という結果でした。

このままではもちろんやばいのでひたすら公式ドキュメントを眺め、 それでもわからない部分は実際にサービスを構築して確認したりしました。

そんなこんなで受験結果としては合格ライン720点のところ739点を取得し無事合格です!

aws-saa

対して何もしてないように見えますが、 AWSの研修を受けていたり今までなんとなく触ったことがあるレベルの前提がある話なので、 これだけでよいのだと思わずに自分にあった方法で勉強すると良いとでしょう。

どうして今までわからなかったのか

研修を行い資格までとった現在から見て、3月までの自分はどうして理解できていなかったかを振り返ります。

大きな点としては実際にアプリケーションを動かすために必要な様々な技術要素を考慮出来ていなかったというのが大きいです。

例えばEC2にアプリを乗っければいい感じに動くんでしょと思っていたこともありました。

実際にはVPC内のどのサブネットの配置して、ポートの開放やルーティング、 インターネットへの経路など考慮すべきことはたくさんあります。

こういった実際に外へ公開するのに必要な要素を理解せず、 「静的なファイルはS3。 アプリケーションはEC2/ECS。 データベースはRDS。 これさえあれば全部動く」 と考えていました。

特にインターネットに関しては、手元の開発用マシン等ではつながっているのが当たり前であるので意識の外にあったことは間違いないです。

全てうまくやってくれると思い込んでしまうのではなく、 当たり前に存在するインターネットや物理的なマシン等を考慮できるようになると 一歩進んでクラウドを理解できるとこの4か月で学びました。

どうやって勉強していけばいいのか

実際にトレーニングを行ってもらい、自分でサービスを触り、資格取得を目指すというのが今回の私のルートでした。

しかしAWSのトレーニングは高価で個人だとなかなか出を出せないでしょう。

なので自分のお金を出せる範囲で、AWSのサービスを触って、資格取得を目指すのが現実的だと考えます。

ただ受講した後の感想になりますが、あのトレーニングには高いお金を払うだけの価値が存在するため、 本気で覚える覚悟がある方は21万円を握りしめてトレーニングを受講するのが一番良いでしょう。

勉強する中で気をつけることとしては以下です。

  • 自分がやりたいサービスに対して使おうとしているサービスが適切か
  • そのサービスはどこまでの事をサービスとして請け負ってくれるのか
  • インターネットリソースは何が必要化分かっているか(VPC, NAT, Internet Gateway, Subnet, CIDR、Security Group 1つでもピンとこなかったら要チェック)
  • きちんと図示できるか

特にインターネットリソースに関しては他のコンピューティングリソース(EC2, ECS,lambda等)やデータベースリソース(RDS, Dynamo等)に比べ、 開発時では気にし辛い部分なのでここを考慮できるかどうかが脱初心者への一歩だと考えます。

これを分かっているか確認するには非常に簡単で図示することです。

分かってない部分は書かない/書いてある場所がおかしいということになるので、 セルフチェックあるいはメンティーなど誰かに教える場合にはまずお絵かきから始めると良いです。

また資格を勉強する過程で 自分が知らなかった知識を広く収集したり、 細かい部分の穴埋めになったので非常に良かったです。

もちろん資格をとったからといって素晴らしいアーキテクチャを今すぐ組めるようになるわけではないので、 資格なんて無駄と考えることもありますが、資格取得を目指す中での勉強は確実に自分の糧になるので損はないでしょう。

まとめ

  • AWSが4か月でわかるようになった
  • 図示は分かりやすい知識確認のツール
  • インターネットリソースをよく知ろう
  • 資格の勉強は馬鹿にできない
  • AWSの新卒研修があって、社内に詳しい人がたくさんいて、月1万までAWSとGCPを使えるTechな会社があるらしい

Goのreflect.DeepEqualでガッツリハマった話

2018/12/04

目次

はじめに

この記事は 2018年 アドベントカレンダーとなんの関係もない記事です.

どうも一回も怒られたことがないバイトリーダーのゲインです.

さて本日バイトでModel周りのテストコードを書いていた時に reflect.DeepEqual でガッツリハマったので,共有しておきたいと思います.

ハマったこと

さて皆さんこちらのコードを御覧ください. playgroundはこちら

このコードの実行結果はどうなるでしょう. 実行せずに考えてみてください.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"reflect"
)

type Hoge struct {
	Hoge string `json:"hoge"`
	Huga int    `json:"huga"`
}

func main() {
	m1 := make(map[string]interface{}, 0)
	m1["hoge"] = "hogehoge"
	m1["huga"] = 1

	m2 := make(map[string]interface{}, 0)
	m2["hoge"] = "hogehoge"
	m2["huga"] = 1

	hoge := Hoge{
		Hoge: "hogehoge",
		Huga: 1,
	}
	hogeJSON, err := json.Marshal(hoge)
	if err != nil {
		log.Fatal(err)
	}
	var m3 map[string]interface{}
	err = json.Unmarshal(hogeJSON, &m3)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("m1 == m2?: %t\n", reflect.DeepEqual(m1, m2))
	fmt.Printf("m1 == m3?: %t\n", reflect.DeepEqual(m1, m3))
	fmt.Printf("m2 == m3?: %t\n", reflect.DeepEqual(m2, m3))
}

中身としてはm1,m2は同じキーに対して同じ値を入れた別の変数.

m3に関しては構造体を一度JSONにしてから map[string]interface{} にUnmarshalした変数になってます.

さてこれを実行すると以下のように表示されるでいいですか?

m1==m2?: true
m1==m3?: true
m2==m3?: true

...残念ながら違うんです.

午前中の私は上記になると思ってました.

実際はこうです.

m1 == m2?: true
m1 == m3?: false
m2 == m3?: false

さてどうしてこんなことになるんでしょう.

中身を確認してみましょう

ちょっと改良してこんなコードにしてみました. playgroundはこちら

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"reflect"
)

type Hoge struct {
	Hoge string `json:"hoge"`
	Huga int    `json:"huga"`
}

func main() {
	m1 := make(map[string]interface{}, 0)
	m1["hoge"] = "hogehoge"
	m1["huga"] = 1

	m2 := make(map[string]interface{}, 0)
	m2["hoge"] = "hogehoge"
	m2["huga"] = 1

	hoge := Hoge{
		Hoge: "hogehoge",
		Huga: 1,
	}
	hogeJSON, err := json.Marshal(hoge)
	if err != nil {
		log.Fatal(err)
	}
	var m3 map[string]interface{}
	err = json.Unmarshal(hogeJSON, &m3)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("m1 == m2?: %t\n", reflect.DeepEqual(m1, m2))
	fmt.Printf("m1 == m3?: %t\n", reflect.DeepEqual(m1, m3))
	fmt.Printf("m2 == m3?: %t\n", reflect.DeepEqual(m2, m3))

	fmt.Println("---m1---")
	for _, val := range m1 {
		fmt.Printf("%#v\n", val)
	}

	fmt.Println("---m2---")
	for _, val := range m2 {
		fmt.Printf("%#v\n", val)
	}

	fmt.Println("---m3---")
	for _, val := range m3 {
		fmt.Printf("%#v\n", val)
	}
}

さてこれを実行した結果は以下です. 

m1 == m2?: true
m1 == m3?: false
m2 == m3?: false
---m1---
"hogehoge"
1
---m2---
"hogehoge"
1
---m3---
"hogehoge"
1

おかしい...値も同じなのになんでmapが一致しないんだろう...

reflect.DeepEqual がおかしいんじゃないのか...

とか思い始めてきたので師匠エンジニアにご相談したところ一発で解決したので解説していきます.

なぜこうなったのか

先程のコードにもう少し確認用のコードを追加してみましょう playgroundはこちら

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"reflect"
)

type Hoge struct {
	Hoge string `json:"hoge"`
	Huga int    `json:"huga"`
}

func main() {
	m1 := make(map[string]interface{}, 0)
	m1["hoge"] = "hogehoge"
	m1["huga"] = 1

	m2 := make(map[string]interface{}, 0)
	m2["hoge"] = "hogehoge"
	m2["huga"] = 1

	hoge := Hoge{
		Hoge: "hogehoge",
		Huga: 1,
	}
	hogeJSON, err := json.Marshal(hoge)
	if err != nil {
		log.Fatal(err)
	}
	var m3 map[string]interface{}
	err = json.Unmarshal(hogeJSON, &m3)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("m1 == m2?: %t\n", reflect.DeepEqual(m1, m2))
	fmt.Printf("m1 == m3?: %t\n", reflect.DeepEqual(m1, m3))
	fmt.Printf("m2 == m3?: %t\n", reflect.DeepEqual(m2, m3))

	fmt.Println("---m1---")
	for _, val := range m1 {
		fmt.Printf("val:%#v type:%T\n", val, val)
	}

	fmt.Println("---m2---")
	for _, val := range m2 {
		fmt.Printf("val:%#v type:%T\n", val, val)
	}

	fmt.Println("---m3---")
	for _, val := range m3 {
		fmt.Printf("val:%#v type:%T\n", val, val)
	}
}

最後の確認用のコードに を表示するための %T を追加してみました.

このコードの実行結果はこちらです.

m1 == m2?: true
m1 == m3?: false
m2 == m3?: false
---m1---
val:"hogehoge" type:string
val:1 type:int
---m2---
val:"hogehoge" type:string
val:1 type:int
---m3---
val:"hogehoge" type:string
val:1 type:float64

おや犯人が見えてきましたね.

key huga に入っていた 1 が実はfloat64だったため,reflect.DeepEqualfalseを返していたようです.

さて,ではどうしてjsonに変換した後,それをmapにUnmarshalするとfloat64型になるのかというと, そもそもjsonの仕様が定義されているRFC82591には数字の上限値は定義されていません.

RFC2にあるとおり,の倍精度(つまり64bit)で値を丸めることが精度や運用を考慮し実装されることが多いとのことで,Goもそれに習って実装されてるとということですかね(ここ要出典)

こちらの内容はGo公式のBlog[^JSON and Go]やjson packageのGodoc[^Go doc]にも書いてあります.

とのことでinterfacejsonUnmarshalされるときはjsonのNumber型はfloat64型で割り当てられることになります.

float64の最大値を超える場合はjson.Number を利用したりbig packageを使って独自のUnmarshalerを作成したりすると良いでしょう.

結論

今回はreflect.DeepEqual(というかjson?)でハマった話でした.

私はこの件で推定5時間程度無駄にしているので,気をつけたい所存です.

この件の問題点を一瞬で見抜いた@shogo82148には頭が上がりません.(謝辞)

まとめは以下

  • パット見の値が同じようでも型が違うことはある
  • jsonのNumberはinterfaceの場合,float64にUnmarshalされる.
  • 公式のドキュメントを読め
  • 最強の師匠がいる環境は最高
  • 公式のドキュメントを読め
  • お前が思っているより公式の関数は1億倍正しく動く
  • 公式のドキュメントを(ry

Footnotes

  1. https://tools.ietf.org/html/rfc8259#page-7

  2. https://ieeexplore.ieee.org/document/4610935/references#references [^JSON and Go]: https://blog.golang.org/json-and-go#Decoding [^Go doc]: https://golang.org/pkg/encoding/json/#Unmarshal

exported function should have comment or be unexportedに対抗するためのプログラムを作った

2018/08/25

目次

はじめに

どうも最近落ち着きがないゲインです。

普段の趣味プロとかでexported function hogehoge should have comment~

って言われるのが鬱陶しいのと、

わざわざ手打ちで解決するのもアホらしいのでプログラムで解決してもらいました。

リポジトリはこちら gainings/go-comments

出来ること

プログラムの第1引数にgoのファイル名を食わせると、

should have comment~な関数、変数、構造体に,

//{name} is TODO:need to enter a comment

を追加した状態で標準出力に出力します。

これだけです。

中身としてはASTをゴニョゴニョこねていい感じに判別して、 コメントを追加しているだけです。

用途

流石にこれだけでは使いみちが微妙なので、

自分はvimのcommandに登録しました

こんな感じのscriptを書いて

command! -nargs=? GoComments call g:GoComments() 
 
function! g:GoComments()
  let src=systemlist('go-comments '. bufname(""))
  call writefile(src, expand("%:p"))
  e!
endfunction

これを.vimrcで適当に呼び出して見ました

source ~/vim_script/go-comments/goComments.vim

なお今回はどうせ自分しか使わないだろうと思ったのと、

そこまでvim plugin詳しくない...なため雑な使いみちになりましたが、

将来気力が湧いたらこちらのBlog1を参考にちゃんとしたpluginとして提供してみたいです。

Demo

gas-newfile

ざっくりこんな感じ

GoCommentsと打つと

Export かつ shoud have commentsな奴ら用にコメントが自動で挿入される。

あとはTODOタスクとして忘れずにコメントを入力するだけです。

まとめ

GoのASTを使って自分用vim scirptを作ってみました。

ASTは色々遊べそうで面白いですね。

あとは一応はじめての自作vim-scriptだったんですが、

多分実装が良くないのでちゃんとそこも見直したいと思います

参考文献

Footnotes

  1. http://haya14busa.com/vim-go-client/

KOSEN Conference in Tokyo 2018に参加した

2018/07/16

目次

はじめに

3日間東京に行ってきた、ゲインです。

今回は高専カンファレンスに参加してきたので、感想を書きます。

きっかけ

実は高専カンファレンスへの参加は初めてで、 そもそも今ままであんまりこういうイベントには参加してきませんでした。

大体の場合、遠方で開催されるので、高い交通費を払ってまでも参加する意味があるのか と思っていた自分が居たのも確かです。 (おおよそ新潟から東京まで往復16000円)

しかし今回は学生への交通費支援制度があり、交通費が満額、交通費が満額(大事なことなので2回)でたため、参加を決意しました。

発表を聞いた感想

こちらのリンクのタイムテーブルの中で、自分が聞いた発表について感想を書きます。

KOSEN Conference in Tokyo 2018

実践!イラストでわかりやすく表現する技術

湊川あいさん(@llminatoll)というイラストレーターの方の講演。

自身がGitやDocker等をマンガで初心者向けに伝えるための手法を発表していただいた。

自分自身が、学生に技術を教えるということをしているため、 自分自身困ったなーっていう部分に対してのアンサーが合ってよかった。

特に自分自身が初心者だったことを忘れるという話が一番 わかる〜〜 となり共感できた。

また懇親会でもより深く話を聞けて今後の授業の参考になった。

Dive into the community

うなすけ君(@yu_suke1994)によるコミュニティに参加するのは良いぞという話。

発表を聞いている人に向けて自分が参加しているコミュニティの良さや、 コミュニティに参加することで自分がどう変化したかという話でした。

自分に照らし合わせると、田舎だからイベントないし…という言い訳をしてあまり人と関わってきていなかったので、積極的にコミュニティに関わって行きたいなと思いました。

これは余談ですが、普段のTwitter上の彼は散々ネタにされているのでやべーやつなのかと思っていましたが、実際に合ってみるといい人でしたまる

写真を半自動でアップロードしたい俺たちは

どくぴーくん(@e10kup)によるカメラで撮った画像のアップロード自動化の話。

あんまり写真を取らないので、写真をたくさん撮り、共有する人の苦しみが垣間見えました。

最初に無線でアクセスできるSDカードとかあったよなーって思ってたらちゃんと言及されてて、Luaが動く!ってなった時にふぁっ?!ってなってました。

高専卒がインハウスマーケターになるまで

idemiさんの高専卒からマーケティングに携わっていく話。

indeedで[高専 マーケター]とかで調べると出てこないよねーという下りは面白かったです。

エンジニアからしているとマーケターとか営業の人っていまいち何をしているのか分からないので、マーケターとして考えていることを聞けてよかったです。

今すぐ始める社会とつながるMake

きゅんくんさん(@kyun_kun) によるロボット + 服 の話。

幼い頃にCHROINOというロボットに憧れてという点は後世に思いが受け継がれていっているんだという気がして他人事ながらエモくなった。

一番のエモポイントは"ファッションは服じゃなくて情報"と発表していて、なるほど〜〜(語彙力)という感じだった。

後は”学生のうちにやりたいことをやろう”というのも心に来た。

もっとそれ早く聞きたかったなーというお気持ちです。

デモのロボットもかなりレスポンスが良くてただただ凄いというお気持ちです。

気がついたら企業側で高専プロコンに参加してたって話

yanoshiさん(@yanoshi)による高専プロコンの裏側の話。

高専プロコンに参加したことはないけども、サイコロの話とか色々Twitterで燃えていたのでどんなもんなんだ…というのは興味がありました。

yanoshiさんが持つ参加者としてのお気持ちと、審査員等運営側のお気持ちのDiffが見れて面白かったです。

高専生を、もっと挑戦者に!

りゅーかんさん(@RyuhiKanno)の高専生はもっといろんな視点を持とうねって話。

新潟っていう田舎だと特にありがちで、なんでも良いから仕事を手に入れるために安くても就職しちゃうっていうのはもったいないなと思います。

自分も就活は絶対スーツを着たくなかったし、仕事で1日8時間つらい思いをしたくなかったので、楽しく仕事を出来る環境を選びながら就職しました。

なんでもっと高専生に限らず学生は会社に雑に遊びに行って、会社の雰囲気を確かめてみると良いと思いました。

演じる技術

そーとくさん(@ihcamonoihS)の演劇をやるための技術という話。

あまり演劇を見る経験はないため、全然知らない世界でいい知見になりました。

アイデアを作る技術

じぐそうさん(@neo6120)のアイディアを作り出すという話。

マルコフ連鎖は良いらしい。

以上です。

LT

どの発表も面白くてよかった。

特にうにさん(@64G806)の発表は、LINEからのSlack&Kibela導入は, うわ最近研究室でやったやつだ〜ってなって共感しかありませんでした。

LTじゃなくてもっと時間あったら良かったのになーって発表もあってもったいなさを感じました。

全体的な感想

まだまだ知らない世界ばかりなので、圧倒的な情報の多さにやられています。

上は北海道から下は沖縄まで圧倒的な 多様性を感じました。

このイベントを運営した(@asonas)を始めとした運営陣の方々には感謝しかありません。

現状まだまだ人に発表できるようなコンテンツがないので、次参加する時までには何か発表できるようなネタ&技術力をつけてやっていきたいです。

高専カンファレンス Ruby Hack Challenge - Rails寺子屋特別編

高専カンファレンスの次の日にはピクシブ株式会社さんでやったRuby Hack Challengeに参加してきました。

このイベントはRuby Commiter の笹田さんと遠藤さんを講師にお招きし、Rubyを使って何かをやるのではなく、RubyのインタプリタをHackしよう!といった趣旨のイベントでした。

なので久々のC言語を触って、懐かしい気持ちになりました。

午前は講義形式で一緒にバージョンの表示を変更したり、関数を追加するといった形式で 午後からは自分のやりたいことを実装して、最後に発表するという形式でした。

自分は0バイトでFizzBuzzを実行できるように、Rubyのファイルを読み込んで実行する際に強制的にFizzBuzzが走るようにしてみました。

初学者向けのイベントで、自分がやったような一発ギャグや、やろうとしたけど出来なかった等々を許容する雰囲気でリラックスしてイベントに取り組めました。

これも講師陣やスタッフの方々が良い空気を作り出してくれたおかげだと思います。

2日間イベントに参加しての感想

まだまだ強い高専生はいっぱいいるなと思いました。

普段インターネット上でしか知らないような人たちにも実際に会えたし、まだ知らない人たちにも会えて、とても楽しかったです。

これも運営の人たちや参加者の人達の貢献が合ってこそだと思うので、高専はいいぞ という感じです。

今回は学生枠としての参加でほとんど無料でイベントに参加できたので、 次は社会人として後輩を呼べる様にカンパレンスも頑張っていこうと思います。

最後にすげー楽しかったです。また会いましょう。お疲れ様でした。

Golangのimage Packageを掘り下げる

2018/04/30

目次

はじめに

どうもファイルポインタの気持ちがわからない技術力並なゲインです.

無い内内定が有る内々定になったのでBlogちゃんと運用しようと思います.

今回はちょっとGolangで画像を扱う時に,わけのわからん質問を人にしてしまったので

image packageを掘り下げていこうと思います.

Goで画像を読み込みたかった

pngを読み込む

プロジェクトの仕様として画像ファイルがpngでやってくるらしいです.

これを読み込んで色々と処理したいらしいです.

pngを読み込むサンプルは以下になります.

package main
 
import (
	"fmt"
	"image/png"
	"log"
	"os"
)
 
func main() {
	file, err := os.Open("test1.png")
	if err != nil {
		log.Fatal(err)
	}
	img, err := png.Decode(file)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(img.Bounds().String())
}

これでpngを読み込むことが出来ます.

errになってなければ画像のxyの最小最大の座標が表示されるはずですね.

めでたしめでたし.

jpegも読み込む

急な仕様変更で今までpng画像を読み込めれば良かったものを, 更にjpeg画像も読み込みたいという要望が出てきました.

仕方ないので対応することにしましょう.

package main

import (
	"fmt"
	"image/jpeg"
	"image/png"
	"log"
	"os"
)

func main() {
	file, err := os.Open("test1.png")
	if err != nil {
		log.Fatal(err)
	}
	pngImg, err := png.Decode(file)
	if err != nil {
		log.Fatal(err)
	}
	file, err = os.Open("test1.jpg")
	if err != nil {
		log.Fatal(err)
	}
	jpgImg, err := jpeg.Decode(file)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(pngImg.Bounds().String())
	fmt.Println(jpgImg.Bounds().String())
}

これでjpegも読み込めた.めでたしめでたし.

更に他のフォーマットも追加する.

更に追加で要望が入り,

どんな画像形式が来るかわからないけどいい感じに読み込んで欲しいそうです

対応したく無いものの,残念ながら対応しないといけません.

今までpng.Decode(),jpeg.Decode()していた部分は別の方法で処理しなければ汎用的に処理できないでしょう.

ありがたいことにはimage パッケージにもDecodeがあるしこれが使えそうじゃないですか.

同じように早速使ってみましょう.

package main

import (
	"fmt"
	"image"
	"log"
	"os"
)

func main() {
	file, err := os.Open("test1.png")
	if err != nil {
		log.Fatal(err)
	}
	img, _, err := image.Decode(file)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(img.Bounds().String())
}

なんだGoが勝手にやってくれるのか, じゃあimage/pngimage/jpegなんていらないですね!

...

では実行結果を見てみましょう.

2018/04/18 21:11:38 image: unknown format
exit status 1

おかしい.

さっきまで読めてたファイルを使っている.

今回はpng.Decode(),jpeg.Decode()image.Decode()に置き換えただけなのになんで unkown format と怒られます.

きっとこれはバグに違いない.

...

と思いたいところだがこれが正しい動作になります.

前座が長くなりましたが本題に入っていきましょう.

本題

なぜエラーになったのか.

結論から言えば,

読み込めるフォーマットとして登録されていなかったから

です..

Go側が読み込めるフォーマットとして登録されていないため, unkown format と怒られたのです.

ではGo側に読み込めるフォーマットとして登録するにはどうしたら良いのでしょう.

それは image.RegisterFormat()を読んでやればいいということになります.

image/pngimage/jpegは上記の関数をinit()内で読んでいるため, Decode()が使えるんです.

これはアンダースコア付きでimportしても同じであるため, Web上に出回っているサンプルコードなどは,以下のような形で使われているのをよく見ます.

import (
	_ "image/png"
	_ "image/jpeg"
)

これによりGo側が読み込めるフォーマットを理解することが出来ました.

ではその内部実装はどうなっているのだろうか?

見ていきましょう.

image.RegisterFormat()

まずは定義から

image/format.goの中の記述はこう1

// RegisterFormat registers an image format for use by Decode.
// Name is the name of the format, like "jpeg" or "png".
// Magic is the magic prefix that identifies the format's encoding. The magic
// string can contain "?" wildcards that each match any one byte.
// Decode is the function that decodes the encoded image.
// DecodeConfig is the function that decodes just its configuration.

func RegisterFormat(name, magic string, decode func(io.Reader) (Image, error), decodeConfig func(io.Reader) (Config, error))

実際にimage/pngで呼ばれてるのはこんな感じ.2

func init() {
	image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

ではそれぞれに対応付けて見ていきましょう.

まずはname

これは画像のフォーマットを表す名前.以上.

pngのときは pngだし,jpegのときはjpeg

実際にDecodeする時に,そのフォーマットが何であったかを確認するのに使えるという認識.

次にmagic.

これは画像のヘッダー部の識別情報を表すbyte列です.

それぞれの画像を表す識別情報は以下のようになります.

ヘッダーにはwildcardとして ? が含まれている場合もあります.

画像タイプ識別情報(16進数表記)
jpegff d8
png89 50 4E 47 0D 0A 1A 0A
gif47 49 46 38 ? 61
bpm4D 4D 2A 00
webp52 49 46 46 ? ? ? ? 57 45 42 50 56 50 38
tiff(little-endian)4D 4D 2A 00
tiff(big-endian)49 49 00 2A

Goがデフォルトで使えるのはjpeg,png,gifになりますが,

go get -u golang.org/x/imageすることで上の画像フォーマットも使えるようになります.

パッケージを呼び出すことで,識別情報がファイルの最初に書かれている画像をGoとして扱えるフォーマットに登録できるわけです.

そのチェックは以下のようにシンプルに比較しています1

画像ファイルから,登録されている識別情報の長さだけ読み出して,それが一致するかどうかを1バイトずつ見ていく.

それを全てのimportされた画像ファイルの識別情報分だけ繰り返すと行った感じになります.

ここで登録されていない画像フォーマットの場合は,呼び出し元の関数で,unknown format を返してくれるようになっています.

// Match reports whether magic matches b. Magic may contain "?" wildcards.
func match(magic string, b []byte) bool {
	if len(magic) != len(b) {
		return false
	}
	for i, c := range b {
		if magic[i] != c && magic[i] != '?' {
			return false
		}
	}
	return true
}

次はDecode

decodeは,画像ファイルのイメージデータを img.Image インターフェイスを満たす構造体へ格納してくれます.

pngを例にすると,

RGBA形式だったりグレースケールだったり,それぞれの表現方法に合わせて, データを読み込んでくれます.

各画像フォーマットはデータの格納方法が違うのでぜひ調べてみて下さい.

次はDecodeConfig

これは画像の ‐ 表現方法

  • 高さ
  • 幅 を構造体で返します.

これも Decode のようにそれぞれの画像フォーマットに合わせて上手いこと読み込んでくれます.

独自フォーマットを作って学ぶ

かなり DecodeDecodeConfig の部分を説明をサボってしまったので, 実際にオリジナルのフォーマットを作って読み込んでみようと思います.

新しく作る画像フォーマットは gain という名前にすることにします.

これはなんでも良いです.

この gain画像イメージの構造は

  • 1行目は GAIN\n から始まる
  • 2行目は 画像の幅
  • 3行目は 画像の高さ
  • 4行目から~n行目までピクセルの情報が0 ~ 255 (Glay Scale)でスペース区切り

となっています.

実際の中身はこんな感じ.

GAIN
8
8
0 0 0 0 0 0 0 0
255 255 255 255 255 255 255 255 
0 0 0 0 0 0 0 0
255 255 255 255 255 255 255 255 
0 0 0 0 0 0 0 0
255 255 255 255 255 255 255 255 
0 0 0 0 0 0 0 0
255 255 255 255 255 255 255 255 
0 0 0 0 0 0 0 0
255 255 255 255 255 255 255 255 

さぁこれで立派な画像フォーマットが出来ました.

これを Decode するにはimage.RegisterFormatname,magic,Decode,DecodeConfigを渡してやれば良いわけです.

まずは create.goinit() の部分を御覧ください.

ここでGoに認識してもらうためにimage.RegisterFormatを使いました.

そして大事なのは Decode です.

上の書いてある構造で画像を読み込むための処理が書いてあります.

3行目までがヘッダー情報なので,それぞれ読み込みます.

4行目からのピクセルの情報を幅の分だけ,スペース区切りで分割して読み込みます.

それをgray scale用の構造体に SetGray で保持しています.

これを画像の高さの分だけ繰り返すと行った挙動になっています.

DecodeConfigはヘッダーの情報だけ読み込んで, 高さと幅を構造体に渡しているだけです.

さてこれを実際に image.Decode から読んでみましょう.

画像を作るためのコードも乗っけておくので適当な場所に配置して実行して下さい.

これで独自の画像フォーマット gainimage.Decode 経由でDecodeして jpeg に変換することが出来ました.

他にもGoが対応していない画像フォーマットを読み込む必要が出てきたら,

Decoderを作成して,image.RegisterFormat を使えば良いわけですね!

感想

  • ドキュメントやGoDocをちゃんと読もう
  • imageさえ使えば全ての画像フォーマットが勝手に使えると思っていた過去の俺を殴りたい
  • こういう時,ソースを漁りに行けるGo最高

Footnotes

  1. https://github.com/golang/go/blob/master/src/image/format.go 2

  2. https://github.com/golang/go/blob/master/src/image/png/reader.go

puppeteerでGoogleログインしたりファイルダウンロードして遊んだ

2017/08/24

目次

夏休みほぼ終わりかけのゲインです.

家でダラダラしてたらQiitaからのメールで, "先週いいねが多かった投稿ベスト20"が来てダラダラ見てたら, --headless時代の本命? Chrome を Node.jsから操作するライブラリ puppeteer について が合って面白そうだったのでちょっと遊んでみました. その備忘録をチョロっと書いておきます.

環境設定等

適当にディレクトリを作って移動する.

npm init
npm i -s puppeteer

参考元と特に代わりはないので適当に.

公式に有るサンプルでスクショを撮ってみて動かせるかチェック.

Google認証

このままだとただのパクリ記事なのでちょっとGoogle認証でログインが必要なサイトで試してみることに.

const puppeteer = require('puppeteer');

//環境変数にGoogleのログイン用アドレスとパスワードを設定
const googleAddr = process.env.GOOGLE_ADDRESS
const googlePasswd = process.env.GOOGLE_PASSWD

(async () => {
	const browser = await puppeteer.launch();
	const page = await browser.newPage();
	
	// googleログインが必要なサイトのページへ行く
	await page.goto('https://hogehogefugafuga.com');
  // googleログインのボタンやリンクのセレクターをサイト内から探してくる
	await page.click("li.hogehoge-google")
	await page.waitForNavigation();
	await page.focus('input[type="email"]');
	await page.type(googleAddr);
	await page.click('input#next')
	await page.waitForSelector('input#Passwd')
	await page.focus('input#Passwd');
	await page.type(googlePasswd);
	await page.click('input#signIn')
	//3秒程度待つ
	await page.waitFor(3000)
	//色々処理
	browser.close();
})();

これで間違いなければリダイレクト先に移るはずです.

また2段階認証等が入っていると先へ進むのは困難なため,

セキュリティに十分考慮し自己判断で解除してください.

ファイルをダウンロードする

リンクをクリックしたらファイルダイアログが出てきて何かを保存できる機能がある時に,

それも自動で取ってこれたら嬉しいのでなんとか取得してみます.

const fs = require('fs');

//ログイン処理等終了後
const downloadUrl = 'http://hogehoge.com/csv'
const result = await page.evaluate((downloadUrl) => {
	const xhr = new XMLHttpRequest();
	xhr.open("GET", downloadUrl, false);
	//この辺は適宜変更の必要あり
	xhr.overrideMimeType('text/plain; charSet=Shift_JIS');
	xhr.send();
	return xhr.responseText;
},downloadUrl);

fs.writeFileSync("result.csv", result, {encoding: null});

今回色々試したサイトに置いてあったcsvはsjisで書かれており,何も設定せずにそのまま取得しようとすると文字化けが発生して大変だったので,xhr.overrideMimeType()のところでsjisを指定しました.

取得したいファイルによって変える必要がある部分は各自変えてください.

感想

サクッとpuppeteerを試してみました.

今回自分が裏で作ったのは毎日定時にクレジットカードの利用明細を自動でダウンロードしてきて,今月に使った金額の合計をIFTTTにpushし, それをpushbullet経由でスマホにプッシュ通知送るようにしました. それが良いのかちょっと良く分からないので,ソースとかを乗っけるのはとりあえず辞めておきます.

もっと色々遊んだり,実用的なことが出来そうなので今後も情報を追っかけていきたいと思います. 現状は学内専用でしか見れないファイルとかをごにょごにょして取得しようかなーと.

何か問題点や指摘がありましたらボロクソに叩いてくれるとうれしいです.

参考文献

--headless時代の本命? Chrome を Node.jsから操作するライブラリ puppeteer について

Puppeteer API v0.9.0

Javascriptでのエンコーディング

Restart

2017/08/02

目次

地元のでっかい花火大会の裏で一人悲しくプログラミングしてますどうもゲインです. 来年こそは彼女と花火行きたいです.行きます.

さて弊学的にはそろそろ夏休みが始まります. 就活も12月くらいから解禁?だったような気がしてこのままじゃアカンと思ったので夏休みの自由研究にちゃんとBlog書くことにでもしてみます.

以前作ったBlogは何か気に入らないのでなかったコトにしてHugoで作り直しました.

夏休みやることリスト

とりあえず今回は夏休みをダラダラ過ごさないためにやることリストを定義しておこうと思います.

今のところ考えているのは以下の項目

  • 研究でGUIを作成する Golang + Qt5(therecipe/qt)
  • 請け負った学内のバイトを進めるためにPython使って実験頑張る
  • アルゴリズムについておさらいする
  • Mikanで英語勉強
  • Blogちゃんと書く

研究で使うのもあり主観で好きなのもあり夏休みはGolang強化月間になりそうです. 後はちょっろとPythonいじって基礎固め. 後は提出を終えた英語論文の発表が10月に有るのでそれに向けてリスニング鍛えないと積むので頑張っていきたい所存(TOEIC435点並感)

何はともあれ同年代に負けないように頑張っていきましょう.