【とらのあな主催】オタクが最新技術を追うライトニングトークイベントを開催しました!!

虎の穴ラボのNSSです。
2018/12/12に「【とらのあな主催】オタクが最新技術を追うライトニングトークイベント」を開催しました。
たくさんの方にご応募いただき、当日はほぼ満席となりました。
今回はそんなイベントの様子をレポートしていきたいと思います。

イベント概要

とらのあなのエンジニア部署である「虎の穴ラボ」が主催するライトニングトーク会です。
ライトニングトークの練習をしながら最新技術への導入・入門として一緒に勉強していく会となります。
今回はとらラボメンバー3名 + 一般公募者の方3名の方にライトニングトークをしていただきました。
とらラボメンバー3名は毎週社内で実施している「最新技術共有会」にて現在学習中のテーマを中心に発表しました。
イベントの最後には懇親会もありました。

ライトニングトーク

今回のLTテーマは以下の通りです。

No. テーマ スピーカー
1 Rails環境に最適なVue.js構成を探る JUNE-JUNE(虎の穴ラボ)
2 Golang ポインタについて UM(虎の穴ラボ)
3 LambdaでAlexa Skillを作ってみる NSS(虎の穴ラボ)
4 芳文社作品の聖地巡礼マップを作ってみた Hirosaji 様
5 flutterでの開発(カレンダーはちゃんとみましょう) godan 様
6 CTFと実務の両面から見たセキュリティ(仮) clown_kage 様

この中からとらラボメンバー3名の発表をご紹介します。

1. Rails環境に最適なVue.js構成を探る(JUNE-JUNE(虎の穴ラボ))

Rails + Vue.js環境を作るために戦ったFantiaマンの物語。
Rails上でVue.jsを動かす時のハマりポイントを丁寧に解説。
ネット上の資料が少ないので、彼の資料は貴重です。 f:id:toranoana-lab:20181213153449p:plain f:id:toranoana-lab:20181213154519p:plain

2. Golang ポインタについて(UM(虎の穴ラボ))

世界一わかりやすいポインタ解説。
あとの質問でも真っ先に「その資料ください」と要望が出るほど。 f:id:toranoana-lab:20181213161908j:plain f:id:toranoana-lab:20181213162154j:plain

3. LambdaでAlexa Skillを作ってみる(NSS(虎の穴ラボ))

Amazon Echo Dotの実機を使ったAlexa Skill解説。
実装されたばかりのRuby2.5を早速使ってみました。
ちょっとおとぼけなAlexa。それもまた萌えポイント?
f:id:toranoana-lab:20181213161403p:plain f:id:toranoana-lab:20181213161730p:plain

懇親会

ライトニングトークの後は懇親会も行いました。 お酒片手にピザを食べながら、いろいろな話をしました。

f:id:toranoana-lab:20181213165421p:plain

最後に

今回初めてライトニングトークイベントをやりました。
LTの準備や当日の段取りなどで課題もありましたが、大変楽しいイベントになりました。

一般公募の方がたのLTが面白すぎました。
イベントを盛り上げていただき、本当にありがとうございました。

ぜひ2回目を開催したいと思います。
そしていつかUDXを借り切れるような一大イベントにしたいと思います!!

VyOSを使った純粋なルータ作り

こんにちは、虎の穴ラボのH.Y.です。(激似)


最近、ギガが減るという単語が一般的に使われてきましたね。
というわけで、ネットワークをネタに
VyOSをつかった純粋なルータ(L3パケット交換のみの機能)作りをしてみようと思います。

そもそも、VyOSとはオープンソースのネットワークOSで、
PCに入れてネットワーク機器としてしまうときに使います。
Debianベースですが、Linuxで使えるコマンドは、ほぼ使えません。


今回は、富士通PRIMERGY TX100 S3を使用します。
f:id:toranoana-lab:20181126113554j:plain
(今回は右側のマシン。左側はマイニングマシンとして使ったマシン)
マイニングマシンの記事はこちら
格安サーバを使ったモナコイン向けマイニングマシン - 虎の穴 開発室ブログ



この機種は、LANポートが2つあるのでルータとしても使用しやすいです。

VyOSのインストール

IOSは本家のページからダウンロードします。
VyOS - an Open Source Linux-based Network OS

VyOSのインストールは以下の記事を参考にしました。
VyOS のインストール&初期セットアップ - らくがきちょう

インストール自体は、基本デフォルトでインストールしたので、5分程度で完了しました。

※インストール直後のネットワーク図
f:id:toranoana-lab:20181126125647p:plain

IPアドレスの指定とsshのインストール

ルーターとして使用するために、interfaceにipアドレスを設定します。
ついでに、マシンと作業スペースが遠いのでsshの有効化も行います。

'編集モードに移行'
$ configure

'interfaceのIP設定の確認'
# show interfaces ethernet
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface        IP Address                        S/L  Description
---------        ----------                        ---  -----------
eth0             -                                 u/u
eth1             -                                 u/u

'interfaceのIP設定'
# set interfaces ethernet eth0 address 192.168.100.201/24
# set interfaces ethernet eth1 address 192.168.101.1/24

'sshを有効化'
# set service ssh

'設定のコミットと保存'
# commit
# save

'interfaceのIP設定の確認'
# show interfaces ethernet
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface        IP Address                        S/L  Description
---------        ----------                        ---  -----------
eth0             192.168.100.201/24                u/u
eth1             192.168.101.1/24                  u/u

sshとeth0にIPアドレスを設定したので、

user:vyos
password:インストール時のパスワード
でsshで操作できるようになります。

※設定後のネットワーク図(eth1はスイッチと繫がっているが省略)
f:id:toranoana-lab:20181126150941p:plain

外と繋ぐ。

ブロードバンドルーターにはつながっては居ますが、

# ping 8.8.8.8
connect: Network is unreachable

外のネットワークに接続できません。

# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
       I - ISIS, B - BGP, > - selected route, * - FIB route

C>* 127.0.0.0/8 is directly connected, lo
C>* 192.168.100.0/24 is directly connected, eth0
C>* 192.168.101.0/24 is directly connected, eth1

ルーティングテーブルを見て、デフォルトゲートウェイへのルーティングが存在しないことがわかります。

なので、デフォルトゲートウェイ(ブロードバンドルーター)にパケットを飛ばすためスタティックルーティングを設定します。

# set protocols static route 0.0.0.0/0 next-hop 192.168.100.1
# commit
# save

ブロードバンドルーター(192.168.100.1)の経路の設定を行いました。

# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_req=1 ttl=121 time=3.73 ms
64 bytes from 8.8.8.8: icmp_req=2 ttl=121 time=3.96 ms


# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
       I - ISIS, B - BGP, > - selected route, * - FIB route

S>* 0.0.0.0/0 [1/0] via 192.168.100.1, eth0
C>* 127.0.0.0/8 is directly connected, lo
C>* 192.168.100.0/24 is directly connected, eth0
C>* 192.168.101.0/24 is directly connected, eth1

pingも問題なく帰ってきており、ルーティングテーブルも大丈夫そうです。

ダイナミックルーティングを使いたい

ブロードバンドルーターがダイナミックルーティングであるRIPに対応しているため、
VyOSでも動作しているかを確認してみます。

# set protocols rip interface eth0
# set protocols rip interface eth1
# commit
# save

RIPの広告対象のインターフェイスを設定しました。

# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
       I - ISIS, B - BGP, > - selected route, * - FIB route

R   0.0.0.0/0 [120/2] via 192.168.100.1, 00:00:24
S>* 0.0.0.0/0 [1/0] via 192.168.100.1, eth0
R>* 49.0.0.0/8 [120/2] via 192.168.100.1, eth0, 00:00:24
R>* 123.0.0.0/8 [120/2] via 192.168.100.1, eth0, 00:00:24
C>* 127.0.0.0/8 is directly connected, lo
C>* 192.168.100.0/24 is directly connected, eth0
C>* 192.168.101.0/24 is directly connected, eth1
R>* 220.210.194.0/24 [120/2] via 192.168.100.1, eth0, 00:00:24
R>* 220.210.221.0/24 [120/2] via 192.168.100.1, eth0, 00:00:24
R>* ***.***.***.0/24 [120/2] via 192.168.100.1, eth0, 00:00:24
R>* ***.***.***.0/24 [120/2] via 192.168.100.1, eth0, 00:00:24

※使用しているISPバレてしまうので一部マスクしてます。

RIPでデフォルトゲートウェイを含めたルーティング情報を取得できましたが、
アドミニストレーティブディスタンスの値がスタティックよりも高いので、
実際使われているのは、スタティックのルートを使われています。
(*がついているのが、実際に使われているルート)


なので、スタティックルーティングの設定を削除します。

setの代わりにdeleteを入れれば消えるそうなので

# delete protocols static route 0.0.0.0/0 next-hop 192.168.100.1
# commit
[ protocols static route 0.0.0.0/0 ]
Must add either a next-hop or blackhole for route 0.0.0.0/0

[[protocols static]] failed
Commit failed

あれ?。

# delete protocols static route 0.0.0.0/0
# commit

こちらの方はうまくいきました。
どうやら、deleteは必要最低限の記述でじゃないと、ダメそうです。

# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
       I - ISIS, B - BGP, > - selected route, * - FIB route

R>* 0.0.0.0/0 [120/2] via 192.168.100.1, eth0, 00:00:56
R>* 49.0.0.0/8 [120/2] via 192.168.100.1, eth0, 00:00:56
R>* 123.0.0.0/8 [120/2] via 192.168.100.1, eth0, 00:00:56
C>* 127.0.0.0/8 is directly connected, lo
C>* 192.168.100.0/24 is directly connected, eth0
C>* 192.168.101.0/24 is directly connected, eth1
R>* 220.210.194.0/24 [120/2] via 192.168.100.1, eth0, 00:00:56
R>* 220.210.221.0/24 [120/2] via 192.168.100.1, eth0, 00:00:56
R>* ***.***.***.0/24 [120/2] via 192.168.100.1, eth0, 00:00:56
R>* ***.***.***.0/24 [120/2] via 192.168.100.1, eth0, 00:00:56

スタティックルーティングが消え、RIPのルーティングを使用しています。

いろいろつなぐ

自宅にYAMAHA、NEC、CISCO製のルータが
手持ちになりましたので、マルチベンダー環境でもルーティング情報を交換できているか確認します。
f:id:toranoana-lab:20181126155309p:plain
(メーカーバラバラなのでインターフェイス名もバラバラ。。。)

VyOS

# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
       I - ISIS, B - BGP, > - selected route, * - FIB route

R>* 0.0.0.0/0 [120/2] via 192.168.100.1, eth0, 21:27:28
R>* 49.0.0.0/8 [120/2] via 192.168.100.1, eth0, 21:27:28
R>* 123.0.0.0/8 [120/2] via 192.168.100.1, eth0, 21:27:28
C>* 127.0.0.0/8 is directly connected, lo
C>* 192.168.100.0/24 is directly connected, eth0
C>* 192.168.101.0/24 is directly connected, eth1
R>* 192.168.201.0/24 [120/2] via 192.168.101.201, eth1, 00:00:15
R>* 192.168.202.0/24 [120/2] via 192.168.101.202, eth1, 00:01:20
R>* 220.210.194.0/24 [120/2] via 192.168.100.1, eth0, 21:27:28
R>* 220.210.221.0/24 [120/2] via 192.168.100.1, eth0, 21:27:28
R>* ***.***.***.0/24 [120/2] via 192.168.100.1, eth0, 21:27:28
R>* ***.***.***.0/24 [120/2] via 192.168.100.1, eth0, 21:27:28

CISCO 1812J
f:id:toranoana-lab:20181126165715j:plain

# show ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, + - replicated route

Gateway of last resort is 192.168.101.1 to network 0.0.0.0

R*    0.0.0.0/0 [120/2] via 192.168.101.1, 00:00:03, FastEthernet0
R     49.0.0.0/8 [120/2] via 192.168.101.1, 00:00:03, FastEthernet0
R     123.0.0.0/8 [120/2] via 192.168.101.1, 00:00:03, FastEthernet0
R     192.168.100.0/24 [120/1] via 192.168.101.1, 00:00:03, FastEthernet0
      192.168.101.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.101.0/24 is directly connected, FastEthernet0
L        192.168.101.201/32 is directly connected, FastEthernet0
      192.168.201.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.201.0/24 is directly connected, FastEthernet1
L        192.168.201.1/32 is directly connected, FastEthernet1
R     192.168.202.0/24 [120/1] via 192.168.101.202, 00:00:02, FastEthernet0
R     220.210.194.0/24 [120/2] via 192.168.101.1, 00:00:08, FastEthernet0
R     220.210.221.0/24 [120/2] via 192.168.101.1, 00:00:08, FastEthernet0
R     ***.***.***.0/24 [120/2] via 192.168.101.1, 00:00:05, FastEthernet0
R     ***.***.***.0/24 [120/2] via 192.168.101.1, 00:00:05, FastEthernet0

NEC IX2015
f:id:toranoana-lab:20181126165650j:plain

# show ip route
IP Routing Table - 10 entries, 2038 frees
Codes: C - Connected, S - Static, R - RIP, O - OSPF, IA - OSPF inter area
       E1 - OSPF external type 1, E2 - OSPF external type 2, B - BGP
       * - Candidate default, s - Summary
Timers: Age
R*   0.0.0.0/0 [120/3] via 192.168.101.1, FastEthernet0/0.0, 0:00:23
R    49.0.0.0/8 [120/3] via 192.168.101.1, FastEthernet0/0.0, 0:00:23
R    123.0.0.0/8 [120/3] via 192.168.101.1, FastEthernet0/0.0, 0:00:23
R    192.168.100.0/24 [120/2] via 192.168.101.1, FastEthernet0/0.0, 0:00:23
C    192.168.101.0/24 [0/1] is directly connected, FastEthernet0/0.0, 0:26:11
C    192.168.202.0/24 [0/1] is directly connected, FastEthernet0/1.0, 0:08:02
R    220.210.194.0/24 [120/3] via 192.168.101.1, FastEthernet0/0.0, 0:00:24
R    220.210.221.0/24 [120/3] via 192.168.101.1, FastEthernet0/0.0, 0:00:24
R    ***.***.***.0/24 [120/3] via 192.168.101.1, FastEthernet0/0.0, 0:00:24
R    ***.***.***.0/24 [120/3] via 192.168.101.1, FastEthernet0/0.0, 0:00:24

YAMAHA NVR500
※瘴気が濃いところにあったので、写真はなしです。。。。

show ip route
宛先ネットワーク    ゲートウェイ     インタフェース  種別  付加情報
default             -                    PP[01]    static  filter:500000
default             -                    PP[02]    static  filter:500000
default             -                    PP[01]    static  
49.***.***.***/32    -                    PP[01] temporary  
123.***.***.***/24    -                    PP[02]    static  
123.***.***.***/32    -                    PP[02] temporary  
123.***.***.***/32    -                    PP[02] temporary  
192.168.100.0/24    192.168.100.1          LAN1  implicit  
192.168.101.0/24    192.168.100.201        LAN1       RIP  metric=1 
192.168.201.0/24    192.168.100.201        LAN1       RIP  metric=2 
192.168.202.0/24    192.168.100.201        LAN1       RIP  metric=2 
220.210.194.***/25    -                    PP[02]    static  
220.210.221.***/32    -                    PP[02] temporary  
***.***.***.***/32    -                    PP[01] temporary  
***.***.***.***/32    -                    PP[01] temporary  
#


全機器、問題なく全ルーティング情報を交換できていました。

※NVR500のルーティング情報が多いのはRIPv1を使っているため、自動集約されているからです。

まとめ

ハードウェアの値段にもよりますが、同等の機能を持ったアプライアンス機器よりも安い
(今回は大体1万円ぐらい)
コマンドの使い勝手は、CISCO IOSに近いものが多く、使い慣れている方は学習コストは少ない
(と思います)

今回は、業務用として使うような(?)設定しかしていませんが、
NAPTやファイアウォールなどのブロードバンドルーターとしても設定できますので
余っているPCをルータにしてみたり、ネットワークの勉強としてや、
VPNを作って遊んでみたりしては、いかがでしょうか。

P.S.
NEC IX2015を設定するコマンドを探すのに一番時間がかかりました。


12/20(木) に『とらのあな開発室』の採用説明会を秋葉原で開催します! Webエンジニアがメインの説明会となりますが、デザイナー、ディレクターもあわせて募集していますので、ぜひ気軽にご参加下さい!
yumenosora.connpass.com


虎の穴ではJavascriptエンジニアをはじめとして一緒に働く仲間を絶賛募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
www.toranoana.jp

Ionicでアニメ情報を取得するスマホアプリを作ってみた

こんにちは、虎の穴ラボのS.Sです。

普段は、WEBエンジニアをやっている私ですが、
今回は、Ionicを使って、簡単にスマホアプリを作ってみましたので、
その内容をご紹介させていただきます。

Ionicとは

Ionicとは、Google社のAngularというアプリケーションフレームワークをベースに、HTML5アプリの開発に特化して作られたJavaScriptフレームワーク。

Ionicのココがスゴい!!

上記のとおり、WEB技術を使って、Web/iOS/Androidアプリが同時に開発が可能。(いわゆるハイブリッド開発)
例えば、iOSアプリを開発する場合、Objective-Cもしくは、Swiftの知識が必要になりますが、
Ionicを使えば、それらの知識は必要なく、JavaScriptとHTMLの知識があれば開発出来ちゃうのです!すっごーい!!
またIonicは、Angularを採用しているので、TypeScriptでも書くことができます!(今回はTypeScriptで書いてみました)

その他に、Ionicの詳しい情報や開発環境の構築方法に関しては、以下の本がわかりやすいのでオススメです!
(とらのあな通販サイトでも購入出来ます)
ec.toranoana.shop

実際に作ってみた

試しに今回は、放映中のアニメ作品の情報が取得できるAnime APIを使って、
2018秋アニメの放映作品一覧を表示するアプリを作ってみました。

Anime APIの詳細については、以下をご参照ください。
qiita.com

◼️用意したもの

・Mac(もちろんWindowsでも開発は可能ですが、iOSアプリの実行環境がないため)
・Xcode
・Android Studio

◼️プロジェクトの作成

まずプロジェクトを作ります。(例:anime-api)

$ ionic start anime-api

そのあとテンプレート選択がありますが、今回は「blank」を選択しました。
※ 基本的にこのテンプレートのソースを書き換えて、開発します。

◼️ HTTP通信により、APIからデータを取得する

まずIonicのプロジェクトで、HttpClientを使う為に、app.module.tsに HttpClientModuleを登録します。

anime-api/src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

次に、home.ts に HttpClientを読み込みます。さらに ionViewDidLoad を使って、ページがロードされた時に、GET送信にて、Anime APIを呼び出してます。そのレスポンスをpostsという変数に格納しています。

anime-api/src/pages/home/home.ts

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  posts: any;

  constructor(
    public navCtrl: NavController,
    public http: HttpClient
  ) {}

  ionViewDidLoad(){
    this.http
      .get('http://api.moemoe.tokyo/anime/v1/master/2018/4')
      .subscribe(data => {
        this.posts = data
      })
  }
}
◼️ 画面表示

*ngFor を使って、postsに格納されたデータを全件表示しています。
 title : 作品タイトル
 twitter_account : 公式ツイッターアカウント

anime-api/src/pages/home/home.html

<ion-header>
  <ion-navbar>
    <ion-title>
      2018秋アニメ一覧
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
 <ion-item *ngFor="let p of posts">
   <h2>{{p.title}}</h2>
   <p>Twitter:@{{p.twitter_account}}</p>
 </ion-item>
</ion-content>
◼️ ブラウザで動かしてみる

ソースの改修はこれで完了なので、実際に動かしてみます。

$ ionic serve

するとブラウザが立ち上がり、アプリが起動されます。
もしこの時に、エラーが発生すれば、エラーの詳細を以下のとおり教えてくれます。(ココもスゴい!)

f:id:toranoana-lab:20181126122135p:plain
正常に起動すれば、アプリの画面がブラウザ上に表示されます。
(※Google Chromeのデベロッパーツールを使って、スマホ用に表示しています)
f:id:toranoana-lab:20181126122219p:plain

◼️ iOSで動かしてみる

Ionicは、Apache Cordova をサポートしているので、Cordovaを使ってビルドすることが出来ます。
これにより、HTML5アプリからiOS/Android アプリにコンパイルすることが出来ます。

以下のコマンドで、実際にIonicのプロジェクトから、Xcode用のプロジェクトに変換されます。

$ ionic cordova build ios --prod

すると、/platforms/ios/ の配下に Xcode用のプロジェクトが生成されます。
その中の xcodeproj ファイル(今回だと anime-api.xcodeproj )をXcodeで開きます。

f:id:toranoana-lab:20181126122229p:plain

ビルド完了後、再生ボタンを押すと、エミュレータが立ち上がり、実際にiOSでの動作確認が出来ました!

f:id:toranoana-lab:20181126122239p:plain

◼️ Androidで動かしてみる

iOSと同じくコマンド1発で、Ionicのプロジェクトから、Android用のプロジェクトに変換されます。

$ ionic cordova build android --prod

すると、/platforms/android/ の配下に Android用のプロジェクトが生成されます。
/platforms/android/ の プロジェクトを Android Studio で開きます。

f:id:toranoana-lab:20181126122247p:plain

ビルド完了後、再生ボタンを押すと、エミュレータが立ち上がり、実際にAndroidでも動作確認が出来ました!

f:id:toranoana-lab:20181126122259p:plain


このようにひとつのプロジェクトで、 web/iOS/Android アプリの開発が同時に出来る Ionic は、とても魅力的です。
またWEB技術を使って、開発が出来たので、Objective-Cや、Swiftの知識がない私でも、iOSアプリを開発することが出来ました。

ハイブリッドなアプリ開発に興味がある方には、Ionic オススメです!!

虎の穴ではJavascriptエンジニアをはじめとして一緒に働く仲間を絶賛募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
www.toranoana.jp

CSVで取り込んだデータを綺麗なグラフで表示する

昨今データの重要性が増しており、分析にグラフは欠かせないものとなっていますが
エクセルで打ち込んでグラフを作成するのもあまりかっこよくないですし、
実際の運用者がエンジニアではないことが多く、データ活用が進んでいないように感じます。

今回はCSVから入力されたデータからRuby on Railsのライブラリを利用して綺麗なグラフを出力してデータ分析する方法を考えて行きたいと思います。 将来的にどんなデータにも対応しうることを想定し、SQLに頼らないように作ってみたいと思います。

Gemを選択する

Railsではgemを利用することでグラフを簡単に出すことができます。 グラフを出力するGemはいくつかあるので、比較して行きたいと思います。

lazy_high_charts

Gemfileに以下を記載します。

gem 'lazy_high_charts'

bundle installします。

bundle install --path=vendor/bundle

application.js に以下を追加します。

//= require highcharts/highcharts
//= require highcharts/highcharts-more

公式のRead.meには下記の記載もありますが、こちらを入れると Highcharts Error #16が発生します。 特に支障はないため外します。 highchartsはグラフにアニメーション効果を追加してくれます。*1

//= require highcharts/highstock
class HomeController < ApplicationController
  def index
    category = [1,3,5,7]
    current_quantity = [1000,20000,1500,18000]

    @graph = LazyHighCharts::HighChart.new('graph') do |f|
      f.title(text: '売上')
      f.xAxis(categories: category)
      f.series(name: '売上', data: current_quantity)
    end
  end
end

chartkick

application.js に以下を追加します。

//= require chartkick

Controllerに追記します。

class HomeController < ApplicationController
  def index
    category = [1,3,5,7]
    current_quantity = [1000,20000,1500,18000]

    # Lazy_High_chart
    @lazegraph = LazyHighCharts::HighChart.new('graph') do |f|
      f.title(text: '売上')
      f.xAxis(categories: category)
      f.series(name: '売上', data: current_quantity)
    end

    # Chartkick
    @chartkickgraph = {"1": 1000,"3": 20000,"5": 1500,"7": 18000}
  end
end

Viewは以下のように記載します。

<%= line_chart(@chartkickgraph, curve: false, label: "売り上げ", xtitle: "", ytitle: "個数" ) %>

出力した結果です。

f:id:toranoana-lab:20181116194257p:plain
lazy_high_chartsとchartkickの比較

LazyHighChartsはデフォルトでHighChartsが使えますが、 Chartkickは別途インストールする必要があります。

微妙な差はありますが、個人的にChartkickが気に入ったので、 こちらを使って行くことにします。

CSVインポート

続いてCSVインポートを実装していきます。

Viewの実装

<%= line_chart @chartkickgraph , curve: false, label: "売り上げ", xtitle: "", ytitle: "個数" %>

route.rb

post "/", to: 'home#index'

コントローラーの実装

class HomeController < ApplicationController
  require 'csv'
  def index
    file = params[:file]
    datas = []
    unless file.nil?
      ActiveRecord::Base.transaction do
        CSV.foreach(file.path, headers: true) do |row|
          datas.append(Hash[row])
        end
      end
    end
    @chartkickgraph = datas[0]
  end
end

テスト用CSVを用意します。

カラム1, カラム2, カラム3, カラム4
1, 2, 3, 4

出力結果 f:id:toranoana-lab:20181126155210p:plain

簡単にかつ動きのあるグラフが作れましたが、 グラフを見てみると、ヘッダがそのまま下のメモリになっているようです。 これだとあまり実用性がないので、実用性ある形にしたいです。 そこで、日付ごとに金額を合計してグラフを出力することを目指します。

class HomeController < ApplicationController
  require 'csv'
  def index
    file = params[:file]
    datas = {}
    unless file.nil?
      ActiveRecord::Base.transaction do
        CSV.foreach(file.path, headers: true, encoding: "UTF-8", col_sep:",", quote_char: '"') do |row|
          aggregate(datas, row["集計日"] , row["売上"])
        end
      end
    end
    @chartkickgraph = datas
  end

private
  def aggregate(datas, key , value)
    if datas.has_key?(key)
      datas[key] = datas[key].to_i + value.to_i
    else
      datas[key] = value.to_i
    end
    return datas
  end
end

それっぽいテスト用CSVを用意します。

"商品名","カテゴリー","集計日","売上"
"商品A","マンガ","2018/10/1","1000000"
"商品A","マンガ","2018/10/2","1200000"
"商品B","マンガ","2018/10/1","500000"
"商品C","DVD","2018/10/1","600000"

出力して見ます。 f:id:toranoana-lab:20181126155646p:plain

X軸のラベルがおかしいですが、出力できました。

日付が直せない

X軸の日付ラベルがおかしくなってしまったのが直せず、2時間ほど悪戦苦闘しました。 結論を言うとこれで解決です。

<%= line_chart @chartkickgraph, discrete: true, curve: false, label: "売り上げ", xtitle: "", ytitle: "個数" %>

Viewに以下の設定を追加するだけでした。。。

discrete: true

f:id:toranoana-lab:20181126155523p:plain

データ分析っぽいことをする

CSVからいい感じのグラフは作れましたがこれだけではただグラフができただけでビジネスに全く活用できていません。 販売した商品のうちどれが売れているのかを分析できるようにします。

以下のような形式のオブジェクトを渡せば作成できます。

[{name:"商品A", data: {"2018/10/01": "10000000", ...}},
 {name:"商品B", data: {"2018/10/01": "20000000", ...}}
 ...
]

コントローラーを実装します。
商品名をname:、同じ商品名のデータだけを抽出し、日付ごとに集計した結果をdata:とします。

class HomeController < ApplicationController
  require 'csv'
  def index
    file = params[:file]
    data_groups = []
    unless file.nil?
      logger.info("開始")
      ActiveRecord::Base.transaction do
        CSV.foreach(file.path, headers: true, encoding: "UTF-8", col_sep:",", quote_char: '"') do |row|
          datas = {}
          datas[:name] = row["商品名"]
          data = data_groups.find{|h|h[:name] == row["商品名"]}
          if data.nil?
            aggregate(datas[:data]={}, row["集計日"] , row["売上"])
            data_groups << datas
          else
            aggregate(data[:data], row["集計日"] , row["売上"])
          end
        end
      end      
    end
    @chartkickgraph = data_groups
  end

private
  def aggregate(datas, key , value)
    if datas.has_key?(key)
      datas[key] = datas[key].to_i + value.to_i
    else
      datas[key] = value.to_i
    end
    return datas
  end
end

テストデータ

"商品名","カテゴリー","集計日","売上"
"商品A","マンガ","2018/10/01","1000000"
"商品B","マンガ","2018/10/01","500000"
"商品C","DVD","2018/10/01","600000"
"商品A","マンガ","2018/10/02","800000"
"商品B","マンガ","2018/10/02","1500000"
"商品C","DVD","2018/10/02","1600000"
"商品A","マンガ","2018/10/03","6000000"
"商品B","マンガ","2018/10/03","400000"
"商品C","DVD","2018/10/03","300000"
"商品A","マンガ","2018/10/04","1700000"
"商品B","マンガ","2018/10/04","600000"
"商品C","DVD","2018/10/04","700000"
"商品A","マンガ","2018/10/05","2000000"
"商品B","マンガ","2018/10/05","400000"
"商品C","DVD","2018/10/05","900000"

f:id:toranoana-lab:20181126154738p:plain

出力したいグラフが出力できました。 CSVデータをプログラム上で集計したのでかなり大変でした。 今回はDBを使わずにグラフを生成しましたが、 DBに格納してからデータを抽出する方が圧倒的に楽です(泣)

まとめ

今回はRailsでグラフを出すまでが課題なのでここまでですが、 作業をしているうちに課題ができたので、今後のために列挙しておきます。

  • MongoDBやRedisを利用したNoSQLな分析ツールを作る。
  • 分析したい内容を画面から選択できるようにする。

上記は今後実装していきたいと思います。

今回ご紹介したChartkickはRails以外でも利用できるようなので、 ぜひ使って見てください。

*1:highchartsを商用で利用する場合は有償となります。ご注意ください。http://www.altech-ads.com/Others/Highcharts.htm?gclid=CLOoiPif1MMCFRUGvAodPAEA0g#highcharts

Rails×VSCodeのススメ 〜インストールからブレークポイント利用まで〜

みなさんこんにちわ。


前回、以下の記事を書かせてもらったJJと申します。

とらのあな開発室ご用達の開発ツール紹介 - 虎の穴 開発室ブログ


とらのあな開発室ではどのようなツールを使っているのか、
ということで興味を持っていただいたのですが、あるひとつの疑問がTwitterに書かれておりました。

『え、とらのあなってVSCode(VisualStudioCode)使ってないの!?』


そうなんです。

VSCodeを使っている人がこれまでいませんでした。

なぜVSCodeを使っていなかったのか

理由としてはRails経験者は皆RubyMineを使っており、
新しくRailsを始めたばかりのメンバーは他言語でも使っていたAtomをそのまま使っていたからです。

VSCodeを始めようと思った理由

しかし、実はRailsの実装に最適なのはVSCodeであるという話を耳にしまして、
加えて無料の開発ツールなのに使わないのはマズイのではないか!? と思いAtomからの移行を決意しました。

なにせ、

「ブレークポイントが使える」

とのことで、利用しない手はないと思いました。


今回はRails環境にVSCodeを導入し、ブレークポイントを使えるようにするまでの手順と、
躓いたポイントなどをご紹介いたします。

続きを読む

格安サーバを使ったモナコイン向けマイニングマシン

こんにちは、虎の穴ラボのH.Y.です。

最近、冷えてきましたね。
というわけで、昨年作った暖房代わりに、格安サーバを使った

モナコイン向けマイニングマシン

を作って暖を取ろうと思います。(1年遅い気もしますが・・・)



昔、1万で買った、富士通PRIMERGY TX100 S3を使用します。
PCサーバ PRIMERGY TX100 S3 : 富士通
(今は同型のものは販売していないので、後継機のPRIMERGY TX1310 M3がおすすめです。)
富士通 PRIMERGY TX1310 M3 4GB ディスクレスモデル DP変換ケーブル無し(Celeron G3930/タワー) / PGダイレクト


GPUはZOTAC GeForce® GTX 1060 Mini 3GB
ZOTAC GeForce® GTX 1060 Mini 3GB | ZOTAC
を使用しました。

あと、TX100 S3にはHDD用のSATA用の電源ケーブルしか空きがないので、SATA→VGA6pinの変換ケーブルを購入しました。
Amazon CAPTCHA


マイニング(左側が3ヶ月間動かしたマシンになります。)
f:id:toranoana-lab:20181015151108j:plain
内部は、こんな感じです。
f:id:toranoana-lab:20181015151112j:plain

内部はこんな感じで、グラボ周りは空き空間がほとんどありません。
デュアルファンのグラボは入らないです。


f:id:toranoana-lab:20181015151126j:plain
ぎゅうぎゅう故に、上のグラボのファンが擦れてしまうのでダンボールを挟んでいます。
(結構熱くなるので、良い子は不燃性で不導体な物質でやりましょう)

f:id:toranoana-lab:20181015151116j:plain
グラボの補助電源はSATAから取ります。
この電源ユニットは12Vが2系統あるので、
グラボごとに系統を分けたほうがいいです。


f:id:toranoana-lab:20181015151121j:plain
電源出力が250W (280W 20秒)です。
GTX 1060のTDPが120Wなので
120x2 =240w
マイニングとは関係ない理由でXeonE3 1220v2が載っているので
TDP69W・・・
とても、動かなさそうな感じはしますが、マイニングとはいえどフルパワーで動くわけではないですし、
TDP=最大消費電力ではないので、大丈夫だったりします。



f:id:toranoana-lab:20181015151134j:plain
では、マイニングの設定作業を行っていきます。

今回は、Ubuntu18.04のデスクトップ版を使って設定を行いました。
公開するサーバーでないので、全てrootで設定していきます。

あと、個人的な環境の話にはなるのですが、
モニターを一々切り替えるのがめんどくさいので、
パッケージをアップデートしたあとは、sshの操作を基本で行っていきます。

sudo su
apt-get update
apt-get upgrade -y
apt-get install ssh git -y # デスクトップ版はsshがないのでここでインストールする。
ip address # IPアドレス確認(18.04はifconfigがない・・・)

ここから、ssh接続で設定を行いました。
どうやら、Ubuntu16.04の手順では、コンパイル部分でコケるらしく
↓の記事を参考しながら、コンパイルを行いました。
qiita.com

apt-get install nvidia-driver-390 libcuda1-384 nvidia-cuda-toolkit # GPUのドライバ、cudaのライブラリをインストール
git clone https://github.com/tpruvot/ccminer.git # マイニングソフトccminerを取得
# ccminerはgcc5基準で作成されているため、gcc6,7のremoveとgcc5のインストール
apt-get remove gcc-6
apt-get remove g++-6
apt-get remove gcc-7
apt-get remove g++-7 
apt-get install gcc-5 g++-5
ln -s /usr/bin/gcc-5 /usr/bin/gcc
ln -s /usr/bin/g++-5 /usr/bin/g++
apt-get install libcurl4-openssl-dev libssl-dev libjansson-dev automake autotools-dev build-essential -y # ccminerをコンパイルするためのライブラリをインストール
cd ccminer/
./bilud # ビルドを実行。10分ぐらいかかる

マイニングをするとき、発掘難易度が低い仮想通貨以外は、
マイニングプールを使用したマイニングが基本となります
今回、モナコインを掘るので、モナコインマイニングプールで有名所のvippoolでマイニングを行います。
利用するには、ユーザー登録が必要となります。
https://vippool.net/

ユーザー登録後に、ワーカーを設定していきます。
ワーカーとパスワードを設定後に、仮にDifficultyを16.0に設定します。
f:id:toranoana-lab:20181019143852p:plain
Difficultyの値は、下の表(VIPpoolのワーカーの設定ページの表)を参考にします。
f:id:toranoana-lab:20181019144850p:plain
※Difficultyの値が低すぎるたり、高すぎたりすると効率が落ちるので、
実際にマイニングをしてみて、表通りの適切な値を入力した方がいいです。



ワーカーを設定後、以下のコマンドで、マイニングが始まります。

./ccminer -a lyra2rev2 -o stratum+tcp://stratum1.vippool.net:8888 -u [ユーザー名].[ワーカ名] -p [ワーカーごとのパスワード]

マイニングが始まって、問題なければyes!と表示されます。
f:id:toranoana-lab:20181015150633p:plain
1台あたり、22MH/sで44MH/sのハッシュレートが出ていますが、
やはり、空間が少なく、排熱に問題あるせいで

f:id:toranoana-lab:20181015150638p:plain
ファンが挟まれているGPU0のハッシュレートが18MH/s程度まで下がりました。
排熱がうまく行かず、サーマルスロットリングが動きました。


あと、気になる消費電力ですが、ワットチェッカーがなかったのでAPCのUPSの電力計で図りました。
f:id:toranoana-lab:20181015151142j:plain
アイドリング時は70wぐらいでしたが、

f:id:toranoana-lab:20181015151145j:plain
マイニング時は292wになりました。

電源効率85%ぐらいらしいので、
292*0.85=248.2w
多少誤差があるものの、ほんとにギリギリですね。
※このあと、グラボのサーマルスロットリングが動いて270wぐらいに下がりました。

本気で運用するなら、熱的にも、電源的にもPower Limitiを80%とか、かけるほうが良さそうです。

ちなみにこの状態で、2017/12〜2018/2の間、3ヶ月休まずにマイニングし続けられたので、安定性は抜群です。
流石は、サーバ向けのPCのといったところです。


まとめ
さて、仮想通貨で不労所得ができるのかという問題ですが、
Monacoin Charts
↑のリンクで、ハッシュレートを入力するとどのくらいmonacoinで取得できるかがわかります。
f:id:toranoana-lab:20181019164106p:plain
36[円(発掘金額)]-0.29[kw]*24[h]*25[円/kwh(電気単価)]=-138[円/日]
金額的には、騒音がする暖房器具となります。

去年の真冬に、暖房器具として使っていましたが、
部屋全体を温めるには、熱量が足らず、エアコンの暖房がないと辛かったです。
また、このマシンでディープラーニングをするのも問題なさそうですし、他の用途でも使えそうです。

自分も含めて、マシンを組み、試行錯誤するのが趣味みたいな人が多いと思うので、
自作PCが趣味の人は、グラボが安くなったら、やってみたはいかがでしょうか。

P.S.
このマシン1台に対し、2枚のグラボで動くとは思わなかったので、マシンは2台用意していました・・・


10/30(火) に『とらのあな開発室』の採用説明会を秋葉原で開催します! Webエンジニアがメインの説明会となりますが、デザイナー、ディレクターもあわせて募集していますので、ぜひ気軽にご参加下さい!
yumenosora.connpass.com


虎の穴ではJavascriptエンジニアをはじめとして一緒に働く仲間を絶賛募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
www.toranoana.jp

Google Kubernetes Engineでアプリを動かしてみる

こんにちは、虎の穴ラボのM.Uです。

弊社が運用してるクリエイター支援プラットフォームである Fantia は Google Cloud Platform (以下,GCP)上で稼働しています。業務内では Google Compute Engine(以下,GCE)や Cloud Load Balancing などを利用していますが、今回はGCPの一つであるGoogle Kubernetes Engine(以下,GKE)を触ってみたので、その紹介をしてみたいと思います。

全体の流れ

  1. Kubernetesとは
  2. Node.jsで動く簡単なWebアプリを作成
  3. Docker image作成/ローカルで実行確認
  4. Google Cloud SDKの設定
  5. 作成した Docker imageをContainer Registryへpush
  6. GKEでアプリをデプロイする

1. Kubernetesとは

Kubernetesとは Dockerを使ってコンテナ化したアプリケーションのデプロイやスケーリングなどができるオープンソースのプラットフォームです。
複数サーバでのコンテナを管理する機能を持っていて、実際にコンテナが動作するノードとよばれるサーバーとノードのリソース使用状況を監視してコンテナを起動させるマスターサーバーがあります。その他にも、Dockerイメージを管理するレジストリサーバーも含まれます。
GKEは Kubernetes の機能をGCP内部で実現する仕組みです。例えば、ノードは実際にはGCEのインスタンスとして稼働しています。また Dockerのイメージファイルは Container Registryというサービスで push するようになっていますが、実際には Cloud Storage にバケットが作成されそこに保存されます。
Kubernetes の制御は kubectl コマンドが使えます。

今回はNode.jsで単純にテキストをブラウザに表示するアプリを作成したものをGKEでデプロイまで試します。

2. Node.jsで動く簡単なWebアプリを作成

Kubernetesは複数の Docker 環境をクラスタ化する仕組みです。まずは、Docker環境のWebサービスを動かすDockerイメージを用意する必要があります。
ネット上で一般公開しているNginxが入っている Dockerイメージを使ってもよいですが、せっかくなので簡単なアプリを実装してそれを Docker イメージにするところから初めてみましょう。
まずは、テキストを表示するだけの簡単なアプリを用意します。

main.js というファイルを作って"Google Kubernetes Engine"と表示させます。

var express = require('express');
var app = express();

var server = app.listen(8000, () => {
    console.log("Start express. port:" + server.address().port)
});

app.get('/', (req, res) => {
    res.send('Google Kubernetes Engine')
});


package.json ファイルの定義は以下のような具合で npmコマンドからモジュールのインストールが出来るようにします。 

{
    "name": "hello-server",
    "version": "1.0.0",
    "description": "",
    "main": "main.js",
    "scripts": {
        "start": "node main.js",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "m.u",
    "license": "MIT",
    "dependencies": {
        "express": "^4.16.3"
    }
}


次に Dockerのimageファイルを作成するための定義ファイルを作ります。ファイル名は Dockerfile です。

FROM node:8

ADD package.json /app/package.json
RUN cd /app && npm install
ADD main.js /app/

CMD cd /app && npm start

3. Docker image作成/ローカルで実行確認

 
作成した DockerfileからDockerイメージを作成します。カレントディレクトリに Dockerfile がある事を確認し、次のコマンドを実行します。
ここで気をつけることは Google Container Registryへpushするためにタグ付きで作成することです。

$ docker build -t ホスト名/プロジェクト名/イメージ名:タグ .

ホスト名:push先のリージョンに当たります。他にも gcr.io などがありますが、今回は asia.gcr.io にします。
プロジェクト名:今回利用しているGCPのプロジェクト名です。今回動かすプロジェクト名は mu-sample です。
イメージ名タグ名は任意で命名できます。今回は hello-server:v1 とします。

$ docker build -t asia.gcr.io/mu-sample/hello-server:v1 .

Dockerイメージが出来ました。まずはローカルで動作できるか確認してみましょう。

$ docker run -p 8000:8000 -d hello-server:1.0

http://localhost:8000/でアクセスすると以下のように"Google Kubernetes Engine"と表示されることを確認します。

f:id:toranoana-lab:20181002194826p:plain

4. Google Cloud SDKの設定

Google Cloud SDKのコマンド gcloud を使用しますが、今回はSDKのインストール手順は割愛します。
以降の gcloudコマンドがどのアカウントで実行されるのかを設定します。

$ gcloud config set account アカウント名

※gcloud auth list で現在どのアカウントが設定されているのかが確認できます。

アカウント認証を行います。次のコマンドを実行するとブラウザが起動してログイン認証を求められます。
許可ボタンを押すとコンソール側ではログインに成功したメッセージが来ます。

$ gcloud auth login

gcloud configを使ってプロジェクトの設定を行います。

gcloud config set project mu-sample

先にGKEのデプロイを行うための kubectlコマンドをインストールしておきましょう。

gcloud components install kubectl

5. 作成した Docker imageをContainer Registryへpush

Docker認証ヘルパーについて

コンテナイメージをContainer Registryへpushする gcloud dockerコマンドが存在しましたが、こちらはDockerクライアントバージョンが1.13以前までとなるようです。
よって、gcloudコマンドではなくdockerコマンドで GCPへの認証を行う必要があります。
Authentication Methods  |  Container Registry  |  Google Cloud

Docker認証ヘルパー ツールをインストールします。

gcloud components install docker-credential-gcr

Container Registryが使えるように認証コマンドを実行します。

docker-credential-gcr configure-docker

次のコマンドでアクセストークンを取得し docker loginを実行させています。

gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://asia.gcr.io

成功すると Login Succeeded と返ってきます。
これで Container Registryへ pushできるようになります。

Container Registryへpush

docker push asia.gcr.io/mu-sample/hello-server:v1

GCPコンソール画面から Container Registry 画面を見てみましょう。
Dockerイメージがリポジトリに新たに作成されていれば成功です。

f:id:toranoana-lab:20180930012941p:plain

6. GKEでアプリをデプロイする

Kubernetes クラスタの作成

以下のコマンドでGKEコマンドで操作するインスタンスを作成します。
最初の説明でもありましたとおり、Kubernetesはコンテナが動作するサーバーをノードと読んでいます。
以下のコマンドの --num-nodesパラメータでノード数を3つにしています。

gcloud container clusters create sample-cluster --num-nodes=3

また、このサーバーはGoogle Compute Engine(以下、GCE)のインスタンスです。
コンソールからGCEの画面へいくとVMインスタンスが3つ立ち上がっているのが確認できます。

アプリケーションのデプロイ

ついにデプロイです。 hello-server は任意の名前です。
GKEのワークロードの名前として管理されます。ポートの指定は今回のアプリは8000番を指定しているので
コンテナーでも公開するポートを同一にします。

kubectl run hello-server --image=asia.gcr.io/mu-sample/hello-server:v1 --port 8000

アプリを外部に公開する

一時的に外部から接続できるようにしてみましょう。

kubectl expose deployment hello-server --type=LoadBalancer --port 80 --target-port 8000

GCPコンソールのネットワークを見ると新たにロードバランサーが作成されています。
kubectl get service コマンドでクラスターの情報を取得することが出来ます。

$ kubectl get service
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
hello-server   LoadBalancer   10.31.253.187   xxx.xxx.xxx.xxx   80:30680/TCP   57s

EXTERNAL-IP が外部向けのIPアドレスになります。
ブラウザでアクセスすると、ローカルで実行した時と同様「Google Kubernetes Engine」と表示されます。

サービスとクラスターの削除

このままだとインスタンスが起動したままなので、サービスの停止とクラスターの削除を行います。

$ kubectl delete service hello-server
$ gcloud container clusters delete sample-cluster

最後に

GKEがどのような操作を通してKubernetesのデプロイを実現しているのかを見ていきました。
今回はデプロイ作業のみに絞って Kubernetes の概要を割愛しています。
公式ドキュメントも充実していますので、Kubernetes をすぐに体験したいならば、GKEを使ってみてはいかがでしょうか。


10/30(火) に『とらのあな開発室』の採用説明会を秋葉原で開催します! Webエンジニアがメインの説明会となりますが、デザイナー、ディレクターもあわせて募集していますので、ぜひ気軽にご参加下さい!
yumenosora.connpass.com


虎の穴ではJavascriptエンジニアをはじめとして一緒に働く仲間を絶賛募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧下さい。
www.toranoana.jp