Fire Engine

化学の修士号→消防士→ITエンジニア(2016年11月〜)

ユーザ名から特定したホストにコマンドを実行するSSHプロキシを書いてみる

今回は、勉強のために簡単なSSHプロキシサーバを実装してみました。
動作としては、ユーザがプロキシサーバに対してSSH接続した際に、ユーザ名からプロキシ先ホストを動的に決定し、SSH接続します。そして、接続したホストに対してhostnameコマンドを実行し、実行結果をクライアント側が受け取るという感じのものです。

やりたいこと

今回の実装はまだ実用的なものではありませんが、最終的にはSSHのユーザ名ベースで接続先のバックエンドを切り替えられるSSHプロキシサーバを構築したいと思っています。
イメージは下のような感じです。

f:id:hirotsuru314:20180722151037p:plain

これは一見ProxyCommandなどを使って踏み台サーバ経由でSSH接続をしているだけのように見えるのですが、大きな違いは ユーザ側が接続先サーバのことを全く意識しない ことです。
ProxyCommandの場合、ユーザ側が接続先サーバと踏み台サーバの情報を知っていますが、私がやりたいことは、ユーザはSSHプロキシサーバにSSH接続するだけで、勝手に特定のホストに接続が振り分けられる、というものです。

実際にこのような動作をするSSHプロキシサーバはOSSとして存在します。

github.com

このsshpiperは非常によくできていて、ユーザと接続先サーバの紐付け情報をMySQLで管理して、SSH接続をトリガーに接続先ホストの情報をDBからSELECTするような処理も実装されています。
一方で、DBに投げるクエリがコードに直書きされていて、使う側がテーブル構成等を合わせないといけなかったり、若干柔軟性に欠ける実装になっています。

コード

github.com

package main

import (
    "github.com/gliderlabs/ssh"
    gossh "golang.org/x/crypto/ssh"
    "log"
    "bytes"
    "errors"
    "io"
    "strings"
)

func findUpstreamByUsername(username string) (string, error) {
    if username == "tsurubee" {
        return "host-tsurubee", nil
    } else if username == "bob" {
        return "host-bob", nil
    }
    return "", errors.New(username + "'s host is not found!")
}

func main() {
    ssh.Handle(func(sess ssh.Session) {
        username := sess.User()
        upstream, err := findUpstreamByUsername(sess.User())
        if err != nil {
            log.Fatal(err.Error())
        }
        log.Printf("Connecting for %s by %s\n", upstream, username)

        config := &gossh.ClientConfig{
            User: username,
            Auth: []gossh.AuthMethod{
                gossh.Password("test"),
            },
            HostKeyCallback: gossh.InsecureIgnoreHostKey(),
        }

        clientConn, err := gossh.Dial("tcp", upstream + ":22", config)
        if err != nil {
            panic(err)
        }
        defer clientConn.Close()

        usess, err := clientConn.NewSession()
        if err != nil {
            panic(err)
        }
        defer usess.Close()

        var b bytes.Buffer
        usess.Stdout = &b
        if err := usess.Run("hostname"); err != nil {
            log.Fatal("Failed to run: " + err.Error())
        }
        r := strings.NewReader(b.String())
        io.Copy(sess, r)
    })

    log.Println("Starting ssh server on port 2222")
    ssh.ListenAndServe(":2222", nil)
}

検証用にDocker環境を用意したのでまずはそちらを立ち上げます。

docker-compose up

これでローカルホストの2222ポートでプロキシサーバが立ち上がり、プロキシ先ホストとして、host-tsurubeehost-bobが立ち上がります。
すると、下記のように、ユーザ名tsurubeeでSSH接続したときは、プロキシサーバ経由で、tsurubee用ホスト(host-tsurubee)でhostnameコマンドを実行した結果がクライアント側に出力され、bobユーザのときはbob用ホスト(host-bob)のhostnameの結果が返ってきています。

$ ssh tsurubee@127.0.0.1 -p 2222
host-tsurubee

$ ssh bob@127.0.0.1 -p 2222
host-bob

このように同じローカルホストの2222番に接続してもユーザ名を元にプロキシされるサーバが振り分けられています。

今回、ユーザ名からプロキシ先ホストを特定するfindUpstreamByUsername関数に簡単に条件分岐を書きましたが、本来この処理はDBから取得するなり、APIサーバから取得するなりしておきたいところです。

また、SSHサーバの構築には、gliderlabs/sshを使いました。これを使うと簡単に指定のポートでListenするSSHサーバを立てられるので便利です。

gliderlabs/sshでListenしたSSHサーバにリクエストがきて、セッションが確立されると、ハンドラーが呼ばれるため、そのハンドラーはプロキシ先に対するSSHクライアントとして動作するように書いています。

最後に

本来は接続先のサーバに対して、ターミナルからサーバにログインした時のように対話的に操作できるようにしたいんだけど、いまいちセッションとかコネクションの取り回しが理解しきれていないため勉強中。
しかし、中途半端でも実装してみると、学びは多かったので、コードファーストで学んでいくスタイルは効率が良さそうに思えた。
もうちょいプロトコルに対する理解や、Golangの抽象化レイヤーへの理解を深めていって、いい感じに書けるようにしていきたい。

PyCon Kyushu 2018 Fukuokaの実行委員をした

2018年6月30日に開催されたPyCon Kyushu 2018 Fukuokaの実行委員をしました。

pycon-kyushu.connpass.com

やったこと

実行委員は主に以下の4つの役割に別れて運営を行いました。

  • 事務局
  • 企画
  • 会場
  • 広報

私はこの中でも会場の担当で、会場のレイアウトを考えたり、当日の設営・撤収の流れを考えたりしました。

参加のきっかけ

私はもともと初めてのエンジニアとしての仕事がPython案件で、非常にPythonには愛着があります。その当時の同僚とPyFukuokaという小さなコミュニティを運営しており、そのコミュニティ活動が今回実行委員をやるきっかけとなりました。
PyFukuokaというコミュニティを始めたきっかけは自分自身のアウトプットの場が欲しいとかそんなんだった気がしますが、コミュニティをやる中で結果としていろんな方々と繋がりができて、今回もこのような素晴らしい機会もいただけたため、改めてコミュニティ活動の大事さを実感しました。

反省点

今回のイベント運営を通して大きな反省点があります。それは私自身「Pythonを多くの方に知ってもらいたい」とか「Pythonをもっと普及したい」といった信念がないままに実行委員として参加したことです。
私はPythonという言語が大好きです。でも、それを広めたいといった気持ちは強くありません。それはおそらく人がどうこう言ってる場合じゃなく、自分が技術・知識をつけることにいっぱいいっぱいだからだと思います。 そういった強い信念がないまま実行委員をした場合、自然な思考回路として、「なるべく時間を割きたくない」という風になってしまいます。
これは本来の実行委員のあるべき姿ではないと思うので、もし次やる機会があれば、強い信念を持って取り組みたい。(持てないならやらない)

まぁ何はともあれ、イベントは大成功!?したのでよかった。
来年は夏に沖縄開催が決定している!!有給取ってバカンスがてらオーディエンスとして参加したい!!

f:id:hirotsuru314:20180702224554j:plain

Site Reliability Engineering – 10章 時系列データからの実践的なアラート

こんにちは、つるべーです。
先日、福岡のインフラ界隈のエンジニアの方々がやっているSRE本の輪読会に参加し、発表をさせていただいたので、その時の内容をまとめます。
私は、10章の「時系列データからの実践的なアラート」を担当させてもらいました。

はじめに

なぜ「時系列データからの実践的なアラート」が必要かを考えてみた。
Webサービスの大規模化や複雑化に伴い、サーバ台数の増加やシステム構成の複雑化が進んだことで、サーバのメトリクス等の情報を高解像度かつ長期間保持したいという要望が高まっている。また、サーバのメトリクスをより統計的に解析し、アラーティングの精度を向上させたいといったシーンも増え、時系列データベースに溜め込んだデータを用いた柔軟なアラーティングの需要が高まっているのではないだろうか。

概要

  • 10章ではBorgmonと呼ばれるGoogleの内部システムについての話が中心だが、「アラートを発するためのデータソースとして時系列データを扱う」という発想は、汎用性が高く、最近ではPrometheus、Riemann、Heka、BosunといったOSSのツールを通じて広く受け入れられている。
  • 大規模なシステムにおいて如何にメンテナンスコストを抑えながら、適切なモニタリングを行うかが重要である。
  • 大量の時系列データを継続的に収集することで、アラーティングの精度・柔軟性を高めることができる。

まとめノート

大規模システムのモニタリングが難しい理由

  • 単にモニタリング対象となるコンポーネントの数が多すぎる。
  • システムに対する責任を負うエンジニアのメンテナンスの負荷を低く保つ必要がある。

大規模システムの場合、1台のマシンが不具合を起こしているくらいでアラートを発するのはノイジー過ぎる。(きちんと冗長化されている場合の話だが)
大規模システムでは、個々のコンポーネントの管理を求めるのではなく、データを適切な粒度で集約して、異常値のみを拾い上げるべきである。
また、必要に応じて個々のコンポーネントの調査もできる粒度に情報を保っておいてくれるようなモニタリングシステムが求められる。

10.1 Borgmonの誕生

  • 大量の収集を行うためにメトリックスのフォーマットは標準化されている必要がある。
  • 単一のターゲットに対する全てのメトリクスの収集を1回のHTTPのフェッチで行えるように定められている。 /varzエンドポイントを叩くとメトリクスが取得できる。
$ curl http://webserver:80/varz
http_requests 37
errors_total 12

10.2 アプリケーションのインスツルメンテーション

  • 全てのエクスポートされた変数をkey/value式のプレーンテキストで返す
  • スキーマレスなテキストベースのインタフェースは、新しいメトリクスを追加するのが簡単である
  • ひとつの変数に対して複数のラベルをつけて出力できるようになった。 例えば、HTTP200のレスポンスが25件、500のレスポンスが12件の場合、下のように出力される。
http_responses map:code 200:25 404:0 500:12

10.3 エクスポートされたデータの収集

  • Borgmonでは、サービスディスカバリを利用してモニタリング対象のリストを動的に生成するため、リストのメンテナンスコストが下がり、モニタリングのスケーラビリティを高めることができている。
    サービスディスカバリを有する点はPrometheusも同じ。PrometheusにはAWS、Azure、GCP、OpenStack、Kubernetesなど多種多様なプラットフォームのメタデータを元にサービスディスカバリすることができるし、Consulとの連携もできる。
  • Borgmonは一定間隔ごとにターゲットのエンドポイント(/varz)を叩いて、結果をメモリに貯める。(Pull型の監視)
  • データの収集をHTTP経由で行っているのは、HTTPのオーバーヘッドが問題になるケースはほとんどないことや、そもそもメトリクスが取れないことそのものを シグナルとして利用することができるからである。

10.4 時系列のアリーナにおけるストレージ

  • データポイントは(timestamp, value)という形式を持ち、 時系列データ と呼ばれる時間順のリストとして保存される。
  • それぞれの時系列データはname=valueという形式で、ユニークなラベルが付与される。 時系列データの概念は、時間と共に進んでいく数値からなる1次元の行列(列ベクトル)であり、変数に対して複数のラベルの組み合わせを追加すると、多次元の行列になる。
  • Borgmonは全てのデータをインメモリデータベースに保存し、定期的にチェックポイントを実行して、時系列データベース(Time-Series Database: TSDB)にアーカイブされる。 Borgmonでは、TSDBに対してもクエリを実行できる。TSDBはインメモリデータベースより低速だが、低コストで大容量である。

時系列データとは(補足)

時系列データとは、時間の推移ととともに観測されるデータのこと。世の中の実務の現場に貯まっていく多くのデータは時系列のデータである。
ITインフラの現場においても、サーバのメトリクス、ネットワークトラフィックなどといった時系列データが時々刻々と蓄積されている。
データ分析において、隣り合う時刻の観測値同士には明らかに相関関係がある(時間依存性を持つ)時系列データの取り扱いは、他のデータ(クロスセクションデータなどと言われる)に比べて、理論的にも実銭的にも難しいとされている。

時系列データベースとは(補足)

時系列データの保存・処理に特化したデータベースである。
Webサービスの大規模化に伴うサーバ台数の増加や、複雑化により、高い解像度の様々なメトリクスを長期間保持したいという要望や、サーバのメトリクスをより統計的に解析し、アラーティングの精度を向上させたいという要望などがあり、時系列データベースの需要が高まっている背景がある。

もちろん、時系列データを格納するためにバックエンドとして、MySQLのようなRDBMSを使用することもできるし、RRD(ラウンドロビンデータベース)のような時系列データに特化したデータベースもある。しかし、最近の時系列データベースでは共通して、時系列データの符号化方式や、メモリもしくはストレージの使用法などのアーキテクチャに工夫がなされており、 ストレージ容量の節約や、解析時に使用するメモリ量の削減などの効果を上げることができる。 また、ほとんどの時系列データベースにおいて、SQL等をベースにしたデータ解析の方法が用意されているという特色もある。

【参考】

階層型のデータストアアーキテクチャについて(補足)

データの保持期間に応じた階層型のデータストアアーキテクチャは、時系列データの保存においてはいくつか事例も見受けられる。

【参考】

10.4.1 ラベルとベクタ

  • 時系列データの名前はラベルセットである。ひとつの時系列データをユニークに探すためには、最低でも次のラベルが必須である。
var    :変数名
job    :モニタリング対象のサーバー の種類
service:サービスを提供するジョブの集合
zone   :データを収集したBorgmonのデータセンタ

これらをまとめて、以下のように変数式として表現できる。

{var=http_requests,job=webserver,instance=host0:80,service=web,zone=us-west}
  • 時系列データに対するクエリでは、これらのラベルセットに対して検索を行うと、マッチする全ての時系列データが返される。

10.5 ルールの評価

  • Borgmonルールと呼ばれるBorgmonのプログラムコードは時系列データから別の時系列データを算出する。
  • ルールの書き方自体は特に重要でない。 ポイントは、「10分間におけるHTTPエラーの比率」といったように、適度な幅で集計をしている点である。(さらにその幅は自由に調整できる)これは時系列にデータを溜め込んでいるからできる。

10.6 アラート

  • 「10分間のエラー率が1%以上で、1秒に1回以上のエラーが 2分以上出るときにアラートをあげる」といったことができるのも時系列データを溜めているからできる。

10.7 モニタリングのトポロジーのシャーディング

  • Borgmonは他のBorgmonから時系列データをインポートできる。そのため、データセンターごとにBorgmonで監視対象の時系列データを取得して、グローバルなBorgmonが各データセンターのBorgmonから時系列データを取得するといったシステム構成が組める。

10.8 ブラックボックスモニタリング

  • Borgmonはホワイトボックスモニタリングであり、調べるのはターゲットのサービス内部の状態のみである。したがって、ブラックボックスモニタリングも組み合わせないと、ユーザーにインパクトのある障害に気づけなくなる場合がある。

10.9 設定のメンテナンス

  • Borgmonではルールの定義をモニタリングの対象となるターゲットから分離しているため、同じルールセットを多くのターゲットに対して一斉に適用できる。これによりモニタリングのメンテナンスコストが大幅に削減できる。

10.10 10年が経過して

  • Borgmonは「アラートの診断のための大量の変数の収集と時系列データに対する集中化されたルールの評価」というモデルである。
  • メンテナンスコストがサービスのサイズに比例しないようにすることがモニタリングをメンテナンス可能にするための鍵である。

最後に…インフラ監視 × データサイエンス

  • Prometheusなどの時系列データベースや次世代監視ツールの発展と人工知能などデータサイエンス分野の発展が相まって、
    サーバの異常状態を予測して自動で制御するような時代が来るかもしれないなどと考えている。
    そのためにも、まずデータを高解像度・長期間溜め込むことは重要だ。さらに、溜め込んだデータを単なる閾値ベースの判定だけでなく、統計学機械学習を駆使したより高度なデータ分析を適用することで、アラーティングの柔軟性を高め、必要なアラートだけを発するようにする。自動制御はその先の世界…

参考

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

インフラエンジニアになって覚えたLinuxコマンド その2(ネットワーク系コマンド編)

こんにちは、つるべーです。
今回の記事は以前の続きで、インフラ業務の中で覚えたLinuxコマンドについてです。

blog.tsurubee.tech

今回はネットワーク系のコマンドに絞ってまとめていきます。

環境

目次

ping

ICMPを使ってネットワーク上のホストの接続を確認する

$ ping www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=63 time=123 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=63 time=123 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=3 ttl=63 time=123 ms
^C
--- www.example.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2005ms
rtt min/avg/max/mdev = 123.096/123.361/123.551/0.345 ms

dig

DNSサーバに問い合わせてドメイン名(またはIPアドレス)を調べる。

dig [@DNSサーバ] ドメイン [検索タイプ]

DNSサーバは指定しない場合は、/etc/resolv.confに記述されたDNSサーバを使用する。
検索タイプはデフォルトではAレコード
-x AAA.BBB.CCC.DDDで逆引き問い合わせもできる。

$ dig google.com

; <<>> DiG 9.10.6 <<>> google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50236
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;google.com.            IN  A

;; ANSWER SECTION:
google.com.     279 IN  A   216.58.197.174

;; Query time: 28 msec
;; SERVER: 192.168.11.1#53(192.168.11.1)
;; WHEN: Fri May 04 23:08:38 JST 2018
;; MSG SIZE  rcvd: 55

+shortオプションをつけると目的の情報だけを表示する

$ dig +short -x 216.58.197.174
nrt12s02-in-f174.1e100.net.
nrt12s02-in-f14.1e100.net.

whois

whoisを利用してドメイン情報を取得する

$ whois gihyo.jp
% IANA WHOIS server
% for more information on IANA, visit http://www.iana.org
% This query returned 1 object

refer:        whois.jprs.jp

domain:       JP

organisation: Japan Registry Services Co., Ltd.
address:      Chiyoda First Bldg. East 13F,
address:      3-8-1 Nishi-Kanda
address:      Chiyoda-ku Tokyo 101-0065
address:      Japan

(省略)

tcpdump

ネットワークインターフェイスを通過するパケットを観察する。
tcpdumpと打つだけでもパケットが流れる様子が観察できるが、大量に流れてくるため通常は以下のように条件を絞って観察する。

tcpdump -i [interface name] port [port number]

実際の実行例は下のような感じ。

$ tcpdump -i eth1 port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
02:09:24.936395 IP 192.168.10.1.50796 > nginx.http: Flags [F.], seq 2273804563, ack 848006729, win 4117, options [nop,nop,TS val 715484926 ecr 28938], length 0
02:09:24.936422 IP 192.168.10.1.50797 > nginx.http: Flags [F.], seq 1504651190, ack 642584441, win 4117, options [nop,nop,TS val 715484926 ecr 28938], length 0
02:09:24.936425 IP 192.168.10.1.50798 > nginx.http: Flags [F.], seq 2028803151, ack 3632381970, win 4117, options [nop,nop,TS val 715484926 ecr 28938], length 0
02:09:24.936428 IP 192.168.10.1.50799 > nginx.http: Flags [SEW], seq 3820785142, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 715484926 ecr 0,sackOK,eol], length 0
02:09:24.936447 IP nginx.http > 192.168.10.1.50799: Flags [S.E], seq 2665692678, ack 3820785143, win 28960, options [mss 1460,sackOK,TS val 43425 ecr 715484926,nop,wscale 6], length 0

(省略)

これを見るだけでもパケット届いてるなーとかまではわかるが、出力結果をちゃんと見るには下の記事がすごく参考になった。

qiita.com

-w ファイル名でファイル出力もでき、出力したファイルをWiresharkで見ると良さそう。

ip

ipコマンドは様々なネットワーク関連の設定を確認/変更できるコマンドである。
CentOS7では、ifconfig、route、netstatなどといったコマンドが入っているnet-toolsパッケージが廃止予定であり、iproute2パッケージに含まれる「ip」「ss」などのコマンドを使用することが推奨されているようです。これについては以下の記事に非常にわかりやすくまとめられています。

enakai00.hatenablog.com

ネットワークデバイスの設定を確認

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 52:54:00:5f:94:78 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0
       valid_lft 83407sec preferred_lft 83407sec
    inet6 fe80::5054:ff:fe5f:9478/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:2e:38:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.11/24 brd 192.168.10.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe2e:38d9/64 scope link
       valid_lft forever preferred_lft forever

aaddrの略。ifconfigコマンドに相当するコマンド。

$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.2.15  netmask 255.255.255.0  broadcast 10.0.2.255
        inet6 fe80::5054:ff:fe5f:9478  prefixlen 64  scopeid 0x20<link>
        ether 52:54:00:5f:94:78  txqueuelen 1000  (Ethernet)
        RX packets 32746  bytes 17741551 (16.9 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 22008  bytes 2394181 (2.2 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.10.11  netmask 255.255.255.0  broadcast 192.168.10.255
        inet6 fe80::a00:27ff:fe2e:38d9  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:2e:38:d9  txqueuelen 1000  (Ethernet)
        RX packets 161  bytes 26250 (25.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 27  bytes 3146 (3.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ルーティングの確認

$ ip r
default via 10.0.2.2 dev eth0 proto static metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.11 metric 100

rrouteの略。routeコマンドに相当するコマンド。

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG    100    0        0 eth0
10.0.2.0        0.0.0.0         255.255.255.0   U     100    0        0 eth0
192.168.10.0    0.0.0.0         255.255.255.0   U     100    0        0 eth1

-nオプション:ホストの名前解決をせずにIPアドレスを表示

上で表示したネットワークデバイスやルーティングの設定は、adddelなどを使って設定情報を追加、削除できる。

ss

ソケットの状態を表示する。

$ ss -tan
State      Recv-Q Send-Q                                            Local Address:Port                                                           Peer Address:Port
LISTEN     0      128                                                           *:111                                                                       *:*
LISTEN     0      128                                                           *:80                                                                        *:*
LISTEN     0      128                                                           *:22                                                                        *:*
LISTEN     0      100                                                   127.0.0.1:25                                                                        *:*
ESTAB      0      0                                                     10.0.2.15:22                                                                 10.0.2.2:50759
LISTEN     0      128                                                          :::111                                                                      :::*
LISTEN     0      128                                                          :::22                                                                       :::*
LISTEN     0      100                                                         ::1:25                                                                       :::*

-tオプション:TCPソケットを表示
-aオプション:接続待ち(LISTEN)も含めて表示
-nオプション:ポート番号を表示(デフォルトではサービス名)
ssコマンドはnetstatコマンドに相当するコマンド。

$ netstat -tan
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN
tcp        0      0 10.0.2.15:22            10.0.2.2:50759          ESTABLISHED
tcp6       0      0 :::111                  :::*                    LISTEN
tcp6       0      0 :::22                   :::*                    LISTEN
tcp6       0      0 ::1:25                  :::*                    LISTEN

参考

[改訂第3版]Linuxコマンドポケットリファレンス

[改訂第3版]Linuxコマンドポケットリファレンス

インフラエンジニアになって覚えたLinuxコマンド その1

こんにちは、つるべーです。
会社のインフラ研修も終わり、4月の2週目から正式にインフラチームにJoinしました。
今回は私が初めてインフラ業務をやっていく中で覚えたLinuxコマンドを備忘録的に書いていきます。まだまだインフラ運用に必要なコマンドはあると思うので、定期的にまとめて書いていこうと思っています。
細かいオプションの使い方等は解説しませんが、どういうときに使えるコマンドであるかと、実行例を書いていきたいと思います。

環境

目次

df

ファイルシステムの使用状況を確認する

$ df -Th
Filesystem                      Type      Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup00-LogVol00 xfs        38G  929M   37G   3% /
devtmpfs                        devtmpfs  236M     0  236M   0% /dev
tmpfs                           tmpfs     245M     0  245M   0% /dev/shm
tmpfs                           tmpfs     245M  4.4M  240M   2% /run
tmpfs                           tmpfs     245M     0  245M   0% /sys/fs/cgroup
/dev/sda2                       xfs      1014M   63M  952M   7% /boot
tmpfs                           tmpfs      49M     0   49M   0% /run/user/1000

-Tファイルシステムの種別(Type)を表示
-h:読みやすいように単位をつけて表示

du

ファイルのディスク使用量を確認する

$ du -hs *
0   bin
30M boot
0   dev
31M etc
19M home
0   lib

(省略)

-h:読みやすいように単位をつけて表示
-s:引数で指定したファイル/ディレクトリの合計容量のみ表示
上の例ではカレントディレクトリの全てのファイルおよびディレクトリの合計容量を表示している。

free

メモリの使用量・空き容量を表示

$ free -h
              total        used        free      shared  buff/cache   available
Mem:           488M         82M         78M        4.4M        328M        373M
Swap:          1.5G          0B        1.5G

-h:読みやすいように単位をつけて表示

ln

ファイルへのリンクを作成

<ハードリンク>
1つのファイルに複数の名前を持たせる
rmコマンドで削除すると指定したファイル名だけが取り除かれファイルの実体は残る。リンクされた最後のファイルが削除されたタイミングでディスク上から削除される。

$ ln file1 file-hl

シンボリックリンク
Windowsのショートカットと同様に、あるファイルを別のファイル名で参照させる。
シンボリックリンクを削除しても元のファイルは削除されない。

$ ln -s file file-sl

$ ls -la
total 0
drwxr-xr-x  2 root    root     33 Apr 29 13:55 .
drwx------. 6 vagrant vagrant 234 Apr 29 13:31 ..
-rw-r--r--  1 root    root      0 Apr 29 13:50 file
lrwxrwxrwx  1 root    root      4 Apr 29 13:55 file-sl -> file

-sシンボリックリンクを作成

lsof

プロセスが開いているファイルを表示する
<ファイル名指定>
ファイルを開いているプロセスを特定

$ lsof /usr/local/nginx/logs/access.log
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
nginx   3202   root    3w   REG  253,0   416000 74101 /usr/local/nginx/logs/access.log
nginx   3629 nobody    3w   REG  253,0   416000 74101 /usr/local/nginx/logs/access.log

<ポート番号指定>
-iで指定したポートを開いているプロセスを特定

$ lsof -i:80
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   3202   root    7u  IPv4  24069      0t0  TCP *:http (LISTEN)
nginx   3629 nobody    7u  IPv4  24069      0t0  TCP *:http (LISTEN)

w

現在サーバにログインしているユーザを確認する

$ w
 12:15:40 up  8:48,  1 user,  load average: 0.00, 0.01, 0.05
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
vagrant  pts/0    10.0.2.2         03:37    4.00s  0.16s  0.00s sshd: vagrant [priv]

tar

アーカイブを作成/展開する
tarは複数のファイルやディレクトリをまとめるだけであり、通常gzipなどの圧縮処理と組み合わせて利用される

<作成>

$ tar czvf dir1.tar.gz dir1
dir1/
dir1/file1.txt
dir1/file2.txt
dir1/file3.txt
dir1/file4.txt

※ オプションにハイフン「-」をつけなくてよい
cアーカイブを作成(Create)
zgzip形式を利用
v:処理したファイル一覧を表示
f:ファイルを指定

<展開>

$ tar xzvf dir1.tar.gz dir1
dir1/
dir1/file1.txt
dir1/file2.txt
dir1/file3.txt
dir1/file4.txt

xアーカイブの展開(eXtract:抽出)

xargs

入力を引数としてコマンドを実行する

$ ls test*
test_1.txt  test_2.txt  test_3.txt

$ ls test* | xargs rm

空白もしくは改行で区切られて文字列を1つずつコマンドに引数として渡して実行する。

tee

標準入力を標準出力とファイルに書き出す

$ ls -la | tee ls.txt
total 116
drwxr-xr-x. 13 root root   169 Apr 29 12:31 .
dr-xr-xr-x. 18 root root   273 Apr 29 12:29 ..
dr-xr-xr-x.  2 root root 20480 Mar 11 08:43 bin
drwxr-xr-x.  2 root root     6 Nov  5  2016 etc
drwxr-xr-x.  2 root root     6 Nov  5  2016 games
drwxr-xr-x. 34 root root  8192 Mar  5 14:56 include
dr-xr-xr-x. 27 root root  4096 Mar  5 14:56 lib

上の標準出力に加えてファイルにも書き出されている

# cat ls.txt
total 116
drwxr-xr-x. 13 root root   169 Apr 29 12:31 .
dr-xr-xr-x. 18 root root   273 Apr 29 12:29 ..
dr-xr-xr-x.  2 root root 20480 Mar 11 08:43 bin
drwxr-xr-x.  2 root root     6 Nov  5  2016 etc
drwxr-xr-x.  2 root root     6 Nov  5  2016 games
drwxr-xr-x. 34 root root  8192 Mar  5 14:56 include
dr-xr-xr-x. 27 root root  4096 Mar  5 14:56 lib

find

ディレクトリツリーからファイルを検索する

$ find . -type f -name "*.txt"
./test.txt
./member.txt
./dev/dev.txt
./dev/test/file.txt

-type:ファイルの種類を指定(f:ファイル、d:ディレクトリ、l:シンボリックリンク
-name:ファイル名を指定(-inameとすると大文字小文字を区別しない)
上の例ではカレントディレクトリ配下の.txtファイルを検索して表示している。

参考

[改訂第3版]Linuxコマンドポケットリファレンス

[改訂第3版]Linuxコマンドポケットリファレンス

ペパカレのインフラ研修を修了した

どうもつるべーです。
私は2018年3月1日から、GMOペパボ本社にてペパボカレッジ(通称ペパカレ)のインフラ研修を受けており、4月6日に無事修了することができました!
今回は研修で学んだことやこれからやりたいことなどを書いていきます。
以前ペパボへの転職エントリーも書いたので、よければ読んでください!

blog.tsurubee.tech

ペパカレって?

ペパカレとは第二新卒エンジニア向け研修のことで、中途で入ってもみっちり研修を受けられます。
私はペパカレ6期生で、これまでWebアプリケーションエンジニアとモバイルアプリケーションエンジニアの採用があったみたいですが、インフラエンジニアのペパカレは今回が初めてだったようです。 ペパボカレッジについては詳しく知りたい方は下の記事を見てください!

www.wantedly.com

やったこと

作業環境ハンズオン

  • zshの導入
  • tmuxの導入
  • ghqとpecoを使ったGit環境の整備(参考

(一言)
ターミナルやエディタ周りはエンジニアにとって最も重要な仕事道具であるため、こだわりを持ってハックすることが大事

Linuxオペレーション基礎

  • Linuxコマンド100本ノック
    • awkコマンドを使ったログの集計
    • grepコマンドによる文字列検索など

(一言)
実際は30本ノックくらいだったかな?一つの処理をするコマンドをパイプで繋いでいき複雑な処理を行っていくところにUNIX哲学を感じた。

オンコール対応

  • 障害原因の調査(ログ・プロセス監視など)
  • サービスの復旧
  • ポストモーテム
作成

(一言)
サンプルのサービス上で意図的に障害を起こしてもらいオンコール対応の疑似体験をした。
障害発生時には「データ(パケット)が流れる順番に沿って調査する」ことが重要だ。

Infrastructure as Code入門

(一言)
いろんな技術に触れまくって超楽しかった。一気に使えるツールが増えた。
Infrastructure as Codeは奥が深いからIaC本を隅々まで読んで復習をしよう。

コマンドラインツール開発

(一言)
初めてコマンドラインツールを作った。Rubyを書くのが楽しくなった。

スクラム研修

  • レゴスクラムを通したチーム開発手法の研修

(一言)
チームでレゴを使って街を作った。大人になってレゴ初めてやったけど普通に楽しい。
チームで作業をするときは、みんなでコミュニーケーションを取り、作りたいものの認識を合わせることが重要だ。

振り返り会

  • KPTを使った週次の振り返り

(一言)
振り返りってこれまでちゃんとやったことがなかったし、その重要性がわかってなかったけど、振り返りの文化はチームでも個人でも超大事だと思った。
何が大事かっていっぱい理由はあるけど「俺たちは前に進んでるんだ」って実感が得られるのが大きいんじゃないかな。

その他(仕事の進め方など)

  • GitHubを使ったPRベースの仕事の進め方
  • Slackの活用方法(スレッドの活用など)
  • コミュニケーション方法
    • 誰かに何かをお願いするときは、いつまでにやってほしいかを明確にする
    • 自分側にあって、相手側にない情報を的確に伝える
    • 遠慮はしない
  • 勉強していて疑問に思ったことはガンガン共有する
  • 何かを習得するときに自分の言葉で説明できるようになる(言語化できること)が重要

(一言)
技術的なところ以外にもいっぱい学びがあるのがペパカレ。

気持ちが変わったこと

自分は成長する存在だと信じられるようになった

これまでエンジニアになって1年間ちょっとの間、自分が成長していると思えなくて悩んでいた時期もあったが、ペパカレでは凄まじいスピードでいろんな技術を習得させてもらったことや、それら学んだ技術を毎週振り返り会で書き出して実感することで、自分の成長を実感できた。
そもそも「自分の成長を信じれないなら、頑張る意味なんてないんだ」と思うようにもなった。

ペパボが大好きだ

  • どんなに小さなことでも褒めてくれる
  • ネガティブな発言はしない
  • みんなと仲良く、でも遠慮はしない
  • 困っているときにいろんな人が手を差し伸べてくれる。
    そんなペパボが大好きになった。

全体を通しての感想

今回のペパカレでのインフラ研修を一言で表現をすると 最高 という言葉しか思い浮かばない。
教えてくださる講師の方々も 最高 、一緒に学ぶ同期も 最高 、会社の雰囲気も 最高 、窓から見える渋谷の街も 最高チリチリネパリコ最高 、最寄りのジムのトレーニング器具も 最高 だった。
ペパカレは本当に頑張る人を支援する仕組みだから、自分と同じようにITとは全く関係のない仕事をしていて、でもITめっちゃやりたいという人にもぜひチャレンジしてほしい。

今後やっていきたいこと

今後は福岡に帰り、インフラ業務をやらせてもらうが、研修でいっぱい学んだとはいえ、広大なインフラの中ではほんの一部であり、まだまだ足りない知識や技術もたくさんあるだろうと思っている。なので、まずはインフラエンジニアとして必要な知識や技術をいち早く習得し、日常的なインフラの業務をこなせるようになることが最優先でやりたいことだ。
その中で、現場の様々な課題に自分自身が直面し、コードを書いて問題解決できるようになりたい。そしてゆくゆくは、自分の得意な領域でかつ誰にも負けない領域を築き上げて、自分にしか作れないものを作ることが夢だ。
あと引き続きベンチプレス150kgを目標に体も鍛え続けたい。

f:id:hirotsuru314:20180408231615j:plain

k近傍法による異常検知のライブラリをmrubyで作ってみた

こんにちは!インフラエンジニア見習いつるべーです。
今回は、mrubyという組込ソフトウェア向けの軽量なRuby言語を使って、k近傍法による異常検知を行うスクリプトを書いてみたので、そちらの紹介です!

目次

なぜ作ったのか

今回は、「何かの問題を解決したい」というよりは、「Rubyの勉強がてらに何か作ってみよう」という動機で作りました。
ただ、一般的なRuby(CRuby)ではなくmrubyを選択したのには理由があります。
私が勤めているGMOペパボでは、mrubyを利用してミドルウェアの振る舞いを設定・制御する仕組み(これを"Middleware Configuration as Code"と呼んでいる)を積極的に導入しており、そこに非常に興味を持ったからです。
下の二つのスライドには、"Middleware Configuration as Code"の概要や事例がまとめられています。

Middleware Configuration as Code - Speaker Deck

Middleware as Code with mruby

作ったもの

ソースコード

github.com

k近傍法に基づく異常検知の理論は最後に書いておくので興味のある方はぜひ!

使い方

mrubyで自分が書いたコードを動かすには、mrubyのコンパイル時に書いたコードを組み込んでコンパイルする必要があります。
したがって、まずはmrubyをコンパイルする環境を用意する必要があります。QiitaにCentOS7上でmrubyをコンパイルして動かすまでの手順を書きましたので、ご参考までに。

qiita.com

mrubyをビルドする前にbuild_config.rbに以下の記述を追加してやると、mruby-knn-detectorを組み込んだmrubyができます。

MRuby::Build.new do |conf|

    # ... (snip) ...

    conf.gem :github => 'tsurubee/mruby-knn-detector'
end

それでは、mirbというCRubyにおけるirbに相当する対話型インタープリタを使って動かしてみます。
※ mruby-knn-detectorは一次元の時系列データの異常検知を行うもので、入力データは一次元配列になります。

$ mruby/bin/mirb
mirb - Embeddable Interactive Ruby Shell

> knn = KNN.new(2, 1)  #(Window size, Number of neighbors)
 => #<KNN:0xdc0fa0 @k=1, @w=2>
> data = [1, 2, 10, 2, 1]
 => [1, 2, 10, 2, 1]
> knn.score(data)
 => [0, 1.4142135623731, 8.0622577482985, 8.0622577482985, 1.4142135623731]

KNNクラスの引数にはスライド窓の幅と近傍数を渡してやり、一次元配列をscore関数に投げ込むと、投げ込んだ配列と同サイズの異常度配列が返ってきます。
(スライド窓や近傍数については最後に解説しています)
返ってきたら異常度配列のうち要素の値が大きいものほど異常度が高いと判断できます。

mrubyに入門するには

mrubyを書き始めるのはCRubyやPythonなどを始めるより少しハードルが高く感じました。それはなぜかというと、コンパイルということに馴染みがなかったからです。私自身ほぼPythonしか書いたことがなかったため、今回初めて自分の書いたコードをコンパイルしました。
それでは、私と同じように「mrubyってちょっと取っつきづらいよ」と感じる方がどうやってmrubyに入門すると良いかということについてですが、私の場合、最初に「WEB+DB PRESS Vol.101」の中で@pyama86さんが執筆された「mrubyを活用したインフラ運用の最前線」を読みました。mrubyの部分は本の中の7ページだけですが、実際にmrubyを利用して開発を行うときに必要な手順がわかりやすく解説されていました。
具体的には、@matsumotoryさんが開発しているmruby-mrbgem-templateを用いたmrbgem開発の基本的な流れが書かれていました。 mruby-mrbgem-templateを用いると、たった数コマンドでmrbgemのひな形が生成されるため、大変便利でした。
mruby-mrbgem-templateの使い方は下の記事に解説がありました。

qiita.com

mrbgemのひな形までできると、あとは普通にRubyを書いていくだけでした。
やりたいことによっては、Linuxシステムコールを利用するような処理をC言語で実装し、C言語のメソッドをRubyから呼び出したりできるようです。
今回私は50行くらいのRubyを書いただけです。

Changefinderとの比較から見るKNNの特徴

mrubyによる異常検知の実装には@matsumotoryさんのmruby-changefinderがあります。今回これとKNNの比較をしてみたいと思います。
用いるデータは、あんちぽさんがmruby-changefinderを使った異常検知例を以前のブログに書いていたので、それを真似てみようと思います。
データの元ネタは「異常検知でGo!」のデータを使っています。
ハイパーパラメータは以下のような値を用いました。(ハイパーパラメータとは人が決定しなければならないパラメータのこと)

# ChangeFinder
cf = ChangeFinder.new(5, 0.01, 10, 0.01, 5)

# KNN
knn = KNN.new(5, 5)

異常検知の結果が以下のグラフになります。比較しやすくするために縦軸は調整しています。

f:id:hirotsuru314:20180401183834p:plain

KNNもなかなかいい形してますね!
ここで両者の違いをいくつか議論します。(アルゴリズム的にどちらが優れているとかいう話ではない)
まず、ハイパーパラメータについてですが、ChangeFinderが5つに対して、KNNは2つしかありません。したがって、ChangeFinderの方が柔軟な設定が可能であり、確率分布がバチっとハマった時は強そうですが、ハイパーパラメータのチューニングが大変だとも言えます。
明示的に確率分布を仮定しない、KNNや特異スペクトル変換などのアルゴリズムは、ハイパーパラメータも少ないですし、様々なデータに頑強な印象です。
ChangeFinderのメリットの一つは逐次的な学習アルゴリズムであるため、初期学習をしておけば、新しい観測点に対して、一つの異常度を返します。そのため、計算も比較的軽量ですし、異常度のデータを受け取る側の実装もシンプルに組めそうです。
一方、KNNは、今の実装だとデータ配列を受け取って、それと同じサイズの異常度配列を返すので、オンラインで異常検知してやるには実装側でインプットのデータ配列をいい感じにずらしながらデータを投げ込んでやる必要があるし、計算もそこそこ重いです。

今後やりたいこと

Ruby・mruby周りで今後やりたいこと。

  • テストを書く
    今回はなるべく早く動かしたかったのでテストの実装をサボりました。Travis CIで自動テストするとこまではちゃんとやりたい。

  • 言語仕様を学ぶ
    下の本をちら見したらmrb_stateとかmrb_valueとか知らないことがいっぱい解説してあったので、ちゃんと読むと言語仕様を学ぶのに良さげな感じ。

eb.store.nikkei.com

オマケ:k近傍法(K-Nearest Neighbor :KNN)に基づく異常検知の理論

k近傍法とは一般的には多クラス分類のアルゴリズムですが、ちょっとした工夫で時系列データの異常検知にも応用することができます。
k近傍法を言葉で簡単に説明すると、「各点から最も近いデータへの距離を計算することで異常度を算出する手法」です。
もう少し詳しく解説していきます。
まず、1次元の時系列データを時間的に隣接したまとまりとして取り出します。これはスライド窓とも呼ばれ、下のようなイメージです。

f:id:hirotsuru314:20180401181356p:plain

この長さwの窓を左から右にずらしていくことで、1次元の時系列データをスライド窓の幅の次元数の列ベクトルの集合に変換することができます。
ここで列ベクトルとは空間上の点のようなものだとイメージして下さい。(正確には、ユークリッド空間の原点から空間内のどこか一点を結ぶ有向線分)列ベクトル同士には近い/遠い、すなわち距離の概念を考えることができます。k近傍法では、ある列ベクトルから距離が近いベクトルをk個取り出して、その距離の大きさをもとに異常度を算出します。
ここで、ベクトル間の「距離」の求め方と、「異常度」の算出方法には任意性があります。
今回の実装では、距離は典型的にはユークリッド距離を用いています。(マハラノビス距離などを使うこともできる)
異常度の計算には、k個の近傍距離の平均を用いました。