ビットの海

ゆるふわソフトウェアエンジニアしゃぜのブログ

Capistrano3を最低限の構成ではじめてみる

やりたいこと

  • Mac OS X前提
  • sshでログインできる環境で、capistranoのタスクファイルを分割して、パイプライン定義して呼び出したい。
  • 最低限の記述で書き始めたい。

install

$ gem install capistrano
$ cap -v
Capistrano Version: 3.6.1 (Rake Version: 10.4.2)

getting start

# ディレクトリつくる
mkdir sample
cd sample

# 初期ファイル生成する
cap install

# とりあえず使わないものを消す
rm config/deploy/*

全体像

.
├── Capfile
├── config
│   ├── deploy
│   │   ├── local.rb
│   │   └── recipes
│   │       ├── hello1.rb
│   │       └── pipeline.rb
│   └── deploy.rb
├── lib
│   └── capistrano
│       └── tasks
└── log

各タスク置き場のディレクトリつくる

mkdir config/deploy/recipes

deploy.rbをかく

config/deploy.rb

lock '3.6.1'
Dir['config/deploy/recipes/*.rb'].each { |plugin| load(plugin) }

local.rbかく(local用の設定ファイルつくる)

config/deploy/local.rb

set :user, ask('user-name', nil)
set :password, ask('user-password', nil)
server '127.0.0.1', port: 22, user: fetch(:user), password: fetch(:password), roles: %w{app}

pipeline.rbをかく(パイプライン制御用)

config/deploy/recipes/pipeline.rb

task :hello do
  after "hello", "hello1:echo1"
end

hello1.rbをかく(実際のタスク)

config/deploy/recipes/hello1.rb

namespace :hello1 do

  task :echo1 do
    on roles(:app) do
      execute 'echo "hello world"'
    end
  end

end

実行例

localhostsshでログインできる状態で。

$ cap local hello --trace

** Invoke local (first_time)
** Execute local
** Invoke load:defaults (first_time)
** Execute load:defaults
Please enter user-name ():
Please enter user-password ():
** Invoke hello (first_time)
** Execute hello
** Invoke hello1:echo1 (first_time)
** Execute hello1:echo1
00:00 hello1:echo1
      01 echo "hello world"
      01 hello world
    ✔ 01 xxx@127.0.0.1 0.272s

こんな感じ

その後の拡張のやり方

  • これから開発していくときは、環境が増えたら、local.rbのように、xxx.rbを配置すればよい。
  • hello1以外のタスクを定義をしたくなったら、hello1.rbのように、xxx.rbを配置すればよい。

おしまい。

踏み台経由のsshでsocks proxyをつくって、jmx接続して監視する話

やること

  • ローカルのMac上のVisualVMから、リモートのLinuxで動いているJVM(Javaアプリケーション)に接続する
  • sshは直接接続できなくて、踏み台経由

環境

ローカルMac OS version

sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G31

ローカルSSH version

 ssh -V
OpenSSH_6.9p1, LibreSSL 2.1.8

java version

java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)

Javaアプリケーションの設定

Javaアプリケーションの起動シェルはこんなかんじ

#!/bin/sh

JVM_OPS="-server -Xms128M -Xmx128M \
-Dcom.sun.management.jmxremote.rmi.port=18080 \
-Djava.rmi.server.hostname=10.x.x.x \
-Dcom.sun.management.jmxremote=true \
-Dcom.sun.management.jmxremote.port=18080 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false"

java ${JVM_OPS} -jar foo.jar >/dev/null 2>&1 &

exit 0

これをたたく。

sshでsocks proxyをつくる

ssh configはこんなかんじ

Host fumidai
  HostName 10.x.x.x
  Port 22
  IdentityFile ~/.ssh/foo.pem
  User user

Host target
  HostName 10.x.x.x
  Port 22
  IdentityFile ~/.ssh/foo.pem
  User user
  ProxyCommand ssh -F ~/.ssh/config -W %h:%p fumidai
  DynamicForward 10000

sshコマンド実行、proxyつくる

ssh -F ~/.ssh/config target

これでproxyがつくられる

VisualVM or JConsoleで接続する

VisualVM

jvisualvm -J-DsocksProxyHost=localhost -J-DsocksProxyPort=10000 -J-DsocksNonProxyHosts=

VisualVMが起動したら、上の例だと、127.0.0.1:18080に対して、JMX接続を追加すればOK

JConsole

jconsole -J-DsocksProxyHost=localhost -J-DsocksProxyPort=10000 -J-DsocksNonProxyHosts=

ここまで至るの長かったわー

fire HD 8 (2016)が来てから1週間

f:id:shase428:20161001164529j:plain

古いiPadを長らく使っていたのですが、電子書籍のリーダーとして見たときにいくつか欠点があって、新しいタブレットを探していました。

iPadというか、iOSの思想的なところなんですが、やっぱり自炊した本、電子書籍書店で買った本なんかを読むときに、不便を感じます。

  • microSDなどの外部メディアが使用できること
  • ftpdなどを用意に立ち上げられること(自宅のLAN内で、PCからの直接転送をしたい)

これらの機能が欲しいなと思い、8インチクラスでいろいろ探していたんですが、ちょうどfire HDの新型が出るということで、世間のレビューも待たずに買うことにしました。

Primeじゃない定価で、16GBモデルを購入。

特に不満はないんですが、感想をちょっとだけ。

  • iPad,iPhoneの液晶に目が慣れたせいか、やっぱり、液晶は見劣りするが、値段相応
  • 解像度はまぁまぁ、本を読むのがメインだと気にならないレベル
  • fireOSのストアは、google playに比べれば、当然アプリは少ないんですが、ノンサポートでAPKを配ってるところもあるのであまり気にならず(例えば、KADOKAWA BookWalkerとか)

12,980で買える8インチタブレットしては上々ではないでしょうか。

Fire HD 8 タブレット 16GB、ブラック

Fire HD 8 タブレット 16GB、ブラック

Amazon Fire HD 8 (Newモデル) 用カバー ブラック

Amazon Fire HD 8 (Newモデル) 用カバー ブラック

自分のsshのpassphrase忘れた愚か者がクラックする話

タイトルそのまんまなんだが...

想定する環境

  • homebrewが使えるMac

John the Ripper導入

brew install john

もし、opensslが入っていなかったら入れておく

brew install openssl

ssh-privkey-crack.cを拾ってきてコンパイル

curl -O https://raw.githubusercontent.com/Boran/lusas/master/ssh-key-crack/ssh-privkey-crack.c
cc -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -o ssh-privkey-crack ssh-privkey-crack.c -lssl -lcrypto 
chmod +x ssh-privkey-crack

いざ解析

john --stdout --incremental | ./ssh-privkey-crack .ssh/{target_key}

まぁ、これはインクリメンタルに探していくわけで、凝ったパスワードの場合やはり時間がかかる。

私の場合は、いくつか候補があって、どれかわからんかったので、候補ファイルを別途つくってそれを食わせてなんとかしました。

wordlist.txtにずらずら候補を並べただけ。

john --stdout --wordlist=./wordlist.txt |./ssh-privkey-crack .ssh/{target_key}

マッチするとこんなんが出る

------------------------------------------------------------------------ -- -
Passphrase match: <xxx>. Found password after 0 seconds and 0 tries.
-------------------------------------------------------------------------- -- -

愚か者の戦いはこれで終わり

参考にしました

ssh 鍵のパスフレーズを忘れたら

tmux-csshで複数サーバに対するカジュアルなオペレーション

このてのソリューションはいくつかありますが、最近は、tmux-csshが向いてる作業はこれでやってます。

本家

想定する環境

  • homebrewがつかえる環境

導入

$ brew install tmux-cssh

実際のオペレーションサンプル

ターミナル(item2など)から (LDAPでホストが同一ユーザー、同一パスワードでログインできる場合)

接続

$ tmux-cssh -u username host1 host2 host3 host4

見た目イメージ

  • tmuxのペインが複数ひらく。[ctrl + b] + n など、tmuxの操作で普通に移動可能
  • 打ったコマンドは全部のhostに送られる

f:id:shase428:20160805142231p:plain

AWS JavaSDKを使ってEMRでjsonログをごにょっとするメモ

はじめに

  • Webの資料も少ないEMRと、AWS JavaSDKを利用して、jsonログをHiveから処理するための流れについて簡単にまとめました。
  • jdbc接続そのものの記述も省略しています。(DataSouceの定義など)

想定する構成

構成図

f:id:shase428:20160404192714p:plain

各種ソフトウェアのバージョン

  • JDK 1.8
  • EMR AMI 4.x
  • AWS JavaSDK 1.10.40
  • Spring Boot 1.x

一般的な注意点

  • Webの情報の新しさに注意
    • EMR AMIは3系と4系でかなり違っています。情報を参照する場合は、EMR AMIのバージョンに注意してください。

EMRクラスタの作成と削除

サンプルコード

EMRクラスタは、EMR APIを利用して作成することができます。クラスタ起動から、シャットダウンまでのサンプルコードを下記に記載します。

Client.Java

public class Client {

    public static void main(String args[]) {
        EmrService emrService = new EmrService();

        // 1.EMRクライアントの初期化
        emrService.init();
        // 2.EMRクラスタの起動
        String jobFlowId = emrService.lunchCluster();
        // 3.EMRクラスタの状態確認(起動中、待機中、など)
        emrService.getClusterStatus(jobFlowId);
        // 4.EMRクラスタのシャットダウン
        emrService.terminateCluster(jobFlowId);
    }

}

EmrService.java


import java.util.ArrayList;
import java.util.List;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.internal.StaticCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.elasticmapreduce.AmazonElasticMapReduceClient;
import com.amazonaws.services.elasticmapreduce.model.ActionOnFailure;
import com.amazonaws.services.elasticmapreduce.model.Application;
import com.amazonaws.services.elasticmapreduce.model.DescribeClusterRequest;
import com.amazonaws.services.elasticmapreduce.model.DescribeClusterResult;
import com.amazonaws.services.elasticmapreduce.model.HadoopJarStepConfig;
import com.amazonaws.services.elasticmapreduce.model.JobFlowInstancesConfig;
import com.amazonaws.services.elasticmapreduce.model.PlacementType;
import com.amazonaws.services.elasticmapreduce.model.RunJobFlowRequest;
import com.amazonaws.services.elasticmapreduce.model.RunJobFlowResult;
import com.amazonaws.services.elasticmapreduce.model.StepConfig;
import com.amazonaws.services.elasticmapreduce.model.TerminateJobFlowsRequest;

public class EmrService {

    private AmazonElasticMapReduceClient emrClient;

    /**
    * AmazonElasticMapReduceClientを生成する
    */
    public void init() {
        AWSCredentials cred = new BasicAWSCredentials("accessKey", "secretKey");
        AWSCredentialsProvider credentialsProvider = new StaticCredentialsProvider(cred);
        emrClient = new AmazonElasticMapReduceClient(credentialsProvider);
        // リージョンを指定
        emrClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
    }

    /**
    * クラスタの起動
    *
    * @return
    */
    public String lunchCluster() {


        Application hive = new Application();
        hive.withName("Hive");// hiveのinstall

        RunJobFlowRequest request = new RunJobFlowRequest()
                .withName("clusterName") // クラスタ名
                .withApplications(hive)
                .withLogUri("s3buket") // S3のバケットのpath
                .withReleaseLabel("emr-4.2.0") // EMR AMIバージョン
                .withServiceRole("EMR_DefaultRole")
                .withJobFlowRole("EMR_EC2_DefaultRole")
                .withVisibleToAllUsers(true)
                .withSteps(this.buildStepConfig().toArray(new StepConfig[0]))
                .withInstances(new JobFlowInstancesConfig()
                        .withInstanceCount(2) // インスタンス数
                        .withKeepJobFlowAliveWhenNoSteps(true)
                        .withPlacement(new PlacementType().withAvailabilityZone("ap-northeast-1a"))
                        .withEc2KeyName("ec2-key-name")
                        .withMasterInstanceType("master-instance-type")
                        .withSlaveInstanceType("slave-instance-type"));

        RunJobFlowResult result = emrClient.runJobFlow(request);
        return result.getJobFlowId(); // jobFlowIdは停止などに必要
    }

    private List<StepConfig> buildStepConfig() {
        List<StepConfig> result = new ArrayList<>();

        String[] args = {
                "s3-dist-cp"
                , "--src", "s3のpath"
                , "--dest", "hdfsのpath"
                , "--groupBy", "正規表現"
                , "--targetSize", "MB指定"
        };

        StepConfig s3DistCpStep = new StepConfig()
                .withName("S3distCp")
                .withActionOnFailure(ActionOnFailure.CONTINUE)
                .withHadoopJarStep(
                        new HadoopJarStepConfig()
                                .withJar("command-runner.jar")
                                .withArgs(args)
                );

        result.add(s3DistCpStep); // サンプルなので、1つしか定義してないが、複数定義可能
        return result;
    }

    /**
    * クラスタの状態を確認する
    *
    * @param jobFlowId
    */
    public void getClusterStatus(String jobFlowId) {
        DescribeClusterResult describeClusterResult = emrClient.describeCluster(new DescribeClusterRequest().withClusterId(jobFlowId));
        System.out.println(describeClusterResult.getCluster().getStatus().toString());
        System.out.println(describeClusterResult.getCluster().getMasterPublicDnsName());
    }

    /**
    * クラスタを停止させる
    *
    * @param jobFlowId
    */
    public void terminateCluster(String jobFlowId) {
        emrClient.terminateJobFlows(new TerminateJobFlowsRequest().withJobFlowIds(jobFlowId));
    }

}

S3から、HDFSへのログのコピー

S3上のログファイルに対して、直接hiveテーブルを作成し、操作することも可能ですが、速度が犠牲になってしまいます。他の要素とのトレードオフですが、今回は、s3-dist-cpを利用して、s3上のログファイルを一定サイズまでマージして、HDFSにコピーすることとします。

実際の処理は、EMRのjobStepで行います。下記のドキュメントを参照してください。 http://docs.aws.amazon.com/ElasticMapReduce/latest/ReleaseGuide/UsingEMR_s3distcp.html

SDKを利用した実装のサンプルは下記になります。(前記、EmrService.javaの抜粋です)


    /**
    * クラスタの起動
    *
    * @return
    */
    public String lunchCluster() {

        Application hive = new Application();
        hive.withName("Hive");

        RunJobFlowRequest request = new RunJobFlowRequest()
                .withName("clusterName") // クラスタ名
                .withApplications(hive)
                .withLogUri("s3buket") // S3のバケットのpath
                .withReleaseLabel("emr-4.2.0") // EMR AMIバージョン
                .withServiceRole("EMR_DefaultRole")
                .withJobFlowRole("EMR_EC2_DefaultRole")
                .withVisibleToAllUsers(true)
                .withSteps(this.buildStepConfig().toArray(new StepConfig[0]))
                .withInstances(new JobFlowInstancesConfig()
                        .withInstanceCount(2) // インスタンス数
                        .withKeepJobFlowAliveWhenNoSteps(true)
                        .withPlacement(new PlacementType().withAvailabilityZone("ap-northeast-1a"))
                        .withEc2KeyName("ec2-key-name")
                        .withMasterInstanceType("master-instance-type")
                        .withSlaveInstanceType("slave-instance-type"));

        RunJobFlowResult result = emrClient.runJobFlow(request);
        return result.getJobFlowId(); // jobFlowIdは停止などに必要
    }

    private List<StepConfig> buildStepConfig() {
        List<StepConfig> result = new ArrayList<>();

        String[] args = {
                "s3-dist-cp"
                , "--src", "s3のpath"
                , "--dest", "hdfsのpath"
                , "--groupBy", "正規表現"
                , "--targetSize", "MB指定"
        };

        StepConfig s3DistCpStep = new StepConfig()
                .withName("S3distCp")
                .withActionOnFailure(ActionOnFailure.CONTINUE)
                .withHadoopJarStep(
                        new HadoopJarStepConfig()
                                .withJar("command-runner.jar")
                                .withArgs(args)
                );

        result.add(s3DistCpStep); // サンプルなので、1つしか定義してないが、複数定義可能
        return result;
    }

JDBCからHiveクエリの実行

EMRで、クラスタが起動後、Hadoop上の、Hiveの操作に移ります。

JDBCアクセスするための事前準備

EMR上のhiveに対し、JDBCアクセスをするためには、専用のドライバをアプリケーションに組み込む必要があります。詳細については、下記のドキュメントを参考にしてください。 http://docs.aws.amazon.com/ElasticMapReduce/latest/ReleaseGuide/HiveJDBCDriver.html

上記ドキュメントに添付されている、

Hive 1.0 JDBC drivers (driver version 1.0.4): https://amazon-odbc-jdbc-drivers.s3.amazonaws.com/public/AmazonHiveJDBC_1.0.4.1004.zip

こちらのzipファイルを解凍すると、readmeと、jarファイルがいくつか添付されてきます。 こちらのjarを直接Javaアプリケーションに組み込んで仕様してください。

jsonログのパーサーを用意

EMRで、jsonログを読み込む際に、Webの多くの資料では、 s3://elasticmapreduce/samples/hive-ads/libs/jsonserde.jar を使う手順が紹介されていると思います。

しかし、こちらは、複雑なjsonに対応できていない、古いバグが放置されている、などの問題がありました。

そこで、今回は、serdeのライブラリとして下記をビルドして使います。

https://github.com/rcongiu/Hive-JSON-Serde

こちらから以下の手順でjarをつくり、s3上の適当な場所に配置して、hiveから利用してください。

git clone https://github.com/rcongiu/Hive-JSON-Serde.git
cd Hive-JSON-Serde
mvn -Dmaven.test.skip=true package

アプリケーションから、EMRへのconnectivity

JDBC接続するためには、IP接続可能な状態でなければいけません。hiveクエリ発行前に、sshトンネルを利用して、hadoopのmasterにセッションを作成します。下記にサンプルコードを記述します。 sshトンネルのために、jschを利用しています。

import java.util.Date;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;


  // クラスタ起動時渡されるMasterPublicDnsNameを仕様します
    public void execute(String masterPublicDnsName) {
            Session session = null;

            String sshUser = "hadoop";
            int sshPort = 22;

            String strRemoteHost = "127.0.0.1";
            int localPort = 10005;
            int remotePort = 10000;

            final JSch jsch = new JSch();
            jsch.addIdentity("AWSで利用できるEC2用pemファイルのpath");
            session = jsch.getSession(sshUser, masterPublicDnsName, sshPort);

            final Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);

            try {
                // ssh接続
                session.connect();
                // portフォワード
                session.setPortForwardingL(localPort, strRemoteHost, remotePort);
                //todo: hiveクエリの発行など
            } catch (Exception e) {
                //todo:
            } finally {
        // sessionをとじる
                session.disconnect();
            }
  }

Hiveテーブルの作成

JDBC経由で以下のようなコードでhiveテーブルを作成します。 HDFS上のログは、HDFS上に、yyyyMMddのディレクトリで、日付ごとに格納されていると想定しています。 よって、日付単位でのパーティションを作成しています。

private JdbcTemplate jdbcTemplate; // springのjdbcTemplate事前に生成しておく

public void createTargetTable(Date targetDate) {
  jdbcTemplate.execute("add jar " + "s3のパス" + "json-serde-1.3.7-SNAPSHOT-jar-with-dependencies.jar");

  jdbcTemplate.execute("drop table if exists sample_table");

  StringBuilder createTable = new StringBuilder();
  createTable.append("CREATE EXTERNAL TABLE IF NOT EXISTS sample_table ( ");
  createTable.append(" Value string,");
  createTable.append(" Id bigint,");
  createTable.append(" )");
  createTable.append(" PARTITIONED BY ( PT STRING )");
  createTable.append(" row format serde 'org.openx.data.jsonserde.JsonSerDe'");
  createTable.append(" with serdeproperties ('paths'='Id,Value')");

  jdbcTemplate.execute(createTable.toString());

  ZonedDateTime targetDay = targetDate.toInstant().atZone(ZoneId.systemDefault());
  ZonedDateTime nextDay = DateUtils.addDays(targetDate, 1).toInstant().atZone(ZoneId.systemDefault());

  StringBuilder partitionTargetday = new StringBuilder();
  partitionTargetday.append("ALTER TABLE sample_table ADD PARTITION ( pt='");
  partitionTargetday.append(targetDay.format(DateTimeFormatter.ISO_LOCAL_DATE));
  partitionTargetday.append("' ) LOCATION '");
  partitionTargetday.append("HDFS上のパス");
  partitionTargetday.append(targetDay.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
  partitionTargetday.append("/'");

  jdbcTemplate.execute(partitionTargetday.toString());

}

JDBC経由でのクエリ発行

テーブル作成後は、通常のJDBC接続で、RDBに接続するように、Hiveクエリの発行が可能です。 実行例を下記に記します。

// 含まれているデータベース一覧を取得
jdbcTemplate.execute("show dabases");

参考資料

プログラミング Hive

プログラミング Hive

いまさらiphone6sに機種変しました

f:id:shase428:20160402234159j:image

auからクーポン来たので、そろそろ5cも辛くなってきたし、SEにするかぁ、と思って行ったら結果6sにしてた。。。

なんか大きい画面を一度見てしまうと、どうにもSEにする気になれず。

しっかしキャリアでふつーに契約すると月額料金相変わらず、すごいことになるよねぇ。MVNOちょっと考えよう(´・_・`)