shiba-hiro’s 備忘録

技術ブログの皮を被ったただの備忘録

開発環境と仮想マシンとの間でのGitを用いたソース共有

ソース共有手段の必要性

先日の記事では、空っぽの環境からスタートして、プログラムを動作させることを実現しました。
ただしそのプログラムの内容は、echoコマンドで書き込んだものだったり特定のコマンドで自動生成された雛形だったりです。
一方で現実の開発では、複数の人がそれぞれのPCのエディタやIDEを用いて編集したソースコードを、ある時点で、デプロイ可能なものにして(ビルドして)、動作させる必要があります。

そこで今回はバージョン管理システムの代名詞、gitを使って、各々の編集した内容を仮想マシンへと共有する仕組みを作っていきます。
なお便宜上「開発環境」という言葉を使っていますが、これは、ここではホストOS側のことです。

shiba-hiro.hatenablog.com

サンプルプロジェクトの作成

今回はBitbucketというサービスを例にとります。
ひとり〜少人数でバージョン管理を行うには十分な機能が、無料で利用できます。

bitbucket.org

ユーザー登録を済ませたら、ホームの左側にあるメニューから、リポジトリの作成を進めます。
今回は、仮にsample-sharing-repoという名称にします。 リポジトリの作成が済むと、ゼロからスタートするときのためのコマンドを参照できるので、とりあえず開発環境からREADMEをコミットし、プッシュします。

$ cd your_work_space
$ git clone https://{your_account}@bitbucket.org/{your_account}/sample-sharing-repo.git
$ cd sample-sharing-repo
$ echo "# My sample-sharing-repo README" >> README.md
$ git add README.md
$ git commit -m "Initial commit"
$ git push -u origin master

bitbucketの当該リポジトリのページをリロードするとREADMEの反映が確認できます。
ここから、仮想マシンへの共有を進めていきましょう。

仮想マシンからgit clone

では仮想マシンを立ち上げます。
vagrant upubuntu/trusty64を起動し、vagrant sshで接続します。

shiba-hiro.hatenablog.com

開発環境と同様に、git cloneするためにはキーペアが必要です。
まずは仮想環境下でキーペアを作成しましょう。

$ ssh-keygen

次に、生成された公開鍵(~/.ssh/id_rsa.pub)の内容をコピーしてbitbucketへ登録します。
bitbucketのsample-sharing-repoから、画面左下の「設定」 > Access keysをクリックすると「鍵を追加」することができます。
Labelは何でも構いません。
Keyという入力欄へ公開鍵の内容をペーストして保存しましょう。

また、cloneする際にHost Key Verificationをスキップしたいので、下記のような設定ファイルを作成しておきます。

$ touch ~/.ssh/config
$ echo "Host bitbucket.org" >> ~/.ssh/config
$ echo "    User git" >> ~/.ssh/config
$ echo "    Port 22" >> ~/.ssh/config
$ echo "    IdentityFile ~/.ssh/id_rsa" >> ~/.ssh/config
$ echo "    StrictHostKeyChecking no" >> ~/.ssh/config

$ cat ~/.ssh/config 
Host bitbucket.org
    User git
    Port 22
    IdentityFile ~/.ssh/id_rsa
    StrictHostKeyChecking no

では仮想マシン側でgit cloneしてみます。

$ sudo apt update
$ sudo apt install -y git

$ git clone git@bitbucket.org:{your_account}/sample-sharing-repo.git

$ ls
sample-sharing-repo

$ cat sample-sharing-repo/README.md 
# My sample-sharing-repo README

gitリポジトリからソースを引っ張ってくることができるようになりました。
原理的にはこのような形で、クラウド上(プライベート、パブリックとも)からソースを参照してビルドすることができます。

Mavenについて 〜 解決したかった問題とMavenができること

前回の記事では、Mavenのインストールから簡単なプログラムの実行までをやってみました。
しかしながら、触れたばかりだと「Mavenって結局なんなのか」が分かりづらかったりします。
なので、今回はMaven自体について調べつつ、その役割をまとめてみたいと思います。

shiba-hiro.hatenablog.com

Mavenが解決したかった問題 〜 simplify the build process

Maven – Welcome to Apache Maven

Apache Maven is a software project management and comprehension tool.

前回も引用しましたが、公式のドキュメントではこの一文が最初に記されています。
Apache Mavenは、ソフトウェアプロジェクトを管理し理解可能なものにするためのツールです」という感じですが、これだけではよくわかりません。

最近、自分が技術を調べるときによく見るのが、「その技術はどういう問題を解決するために出てきたものなの?」という点です。
今回もここに沿ってMavenを理解していきたいと思います。

https://maven.apache.org/what-is-maven.html

ここのIntroductionに、登場の背景などが載っています。
曰く、build processをシンプルにしたい、という意図で始まったプロジェクトのようです。
ざっくり訳すと、

Antで管理していたけれど、ファイルの中身(つまりビルドの方法や内容)はばらばらで、必要なjarはCVSにチェックつけるような形になっていた
→ ビルドプロセスを標準化したい、プロジェクトが何で構成されているかわかりやすく定義したい、プロジェクト情報を簡単に公開したりプロジェクトの種類に依らないjarの共有方法を提供したりしたい、、、
Mavenという一連のプロジェクトが始まった

という感じです。

もう少し噛み砕いてみます。
Maven以前」の問題を考えてみましょう。
Mavenのようなツールが登場するまでは、jarを取得する方法やテストの実行方法、コンパイルして成果物を動作可能なまでに整えていくステップが統一化されていなかったという問題があったと見れます。
スクリプトは秘伝のタレ化するし、新しいjarへの依存も持たせにくかったし、そもそもjarをどこからどれだけどんな順序で取得すればいいのかも、その方法は統一化されていなかったのでしょう。
特定のサイトからインストールするにしてもバージョンが異なり得るし、jarを誰かから手渡ししてもらうような必要もあったはずです。
また開発者が別のプロダクトの開発へ移行したら、それまでのビルドに関するノウハウや手順がほぼ通用しなくなる、という事態が起きていたであろうことも想像に難くありません。
同時に、(外からjarを取得してビルドするのに苦労があるということのほかに)自分たちの成果物やjarを外に提供するときの必要な情報についても、呼び出して使う側がこの有様では定義のしようがありません。

これさえあれば誰のPCからでも一発でビルドできる、しかもその方法はプロジェクト横断的に通用する。

そんなものがあればなぁ、というところからMaven爆誕です。
pomファイルを配ってmvnコマンドを叩けば、あとはよしなにやってくれる、と。しかもこの方法はプロダクトが変わっても一緒やで、と。
pomの書き方の統一や依存解決のためのjarを置く方法の統一によって、これらを実現していきます。
また拡張性が高いため、「自社では、自分のチームではこういうルールを適用したい、こういうツールやアーキテクチャに適合的なものにしたい」みたいな要望も(新規配属者であろうとmvnコマンドひとつで簡単に利用できる形で)追記できます。

Mavenのゴール 〜 comprehend the complete state of a development effort

ドキュメントに戻りましょう。
Maven’s Objectivesの項です。

Maven’s primary goal is to allow a developer to comprehend the complete state of a development effort in the shortest period of time.

がんばって訳すと「Mavenの第一のゴールは、開発者に対して、最短期間で「開発努力の完全な状態を理解すること」を促すということである。」というような意味になります。
では「開発努力の完全な状態を把握すること」とはなんでしょうか。
ここも自分の解釈ですが、「いまこのプロジェクトがどう構成されているか」というこれまでの積み重ねた成果物の内容を理解できることであったり、すぐさまその成果物の挙動を確認できることであったりを指していると考えています。

そして、このゴールを達成するためのサブミッションとして、次のようなものを挙げています。
- ビルドプロセスを簡素にする〜おおまかな内容をpomに書き下すのみでOK,あとはコマンド一発
- 統一化されたビルドシステムを提供する〜設定内容や共有方法のフォーマットはプロジェクト横断的に統一されます
- 質の高いプロジェクト情報を提供する〜名称、構成物、メーリングリストetc
- ベストプラクティスを用いた開発のためのガイドラインを提供する〜「テストソースは分離するけど並行的な階層で用意する」みたいなtipsの利用を可能にする
- 新しい機能への平易なマイグレーションを可能にする〜インストールしてきたものの更新も簡単!

Mavenができること

上述してきたように、もともとはビルドプロセスの簡素化から始まり、そこからさらに広い「開発」をサポートする仕組みを提供するものになってきたことがわかります。
前回の記事で、「mavenは(単なるビルドツールよりも)もっと大きな役割を包含したもの」と記したのはこの意味です。
まあ、それこそ「ビルド」をどう捉えるかで変わってくるんでしょうが。。。

「できること」はめっちゃ広範です。
プロジェクト生成、インストール、コンパイル、テスト、パッケージング、デプロイ(成果物のアップロード)、依存性の確認etc...
それぞれに役割やコマンドがあるものの、これらを統一フォーマットのファイルやリポジトリにもとづいて実行できるようにした、という点が、Mavenという企画のキモでしょう。

なお、Mavenができること、どのようにプロジェクトのライフサイクルをカバーするかについては、下記記事の図を見ると直感的に理解できます。

2. Maven 入門 | TECHSCORE(テックスコア)

キーワードとして出てきているPOMは、Project Object Modelの略称です。
「開発プロジェクト」をモデル化する一個の案です。
利用者はこの雛形に沿ってプロジェクトの情報を書くことができ、Mavenはその雛形を利用することができます。

ここから先もMavenを使っていく予定ですが、基本がわからなくなったら立ち返り、適宜更新していこうと思います。

空っぽのLinuxからJavaを動作させる & Mavenを動作させる

空っぽのOSから一歩を踏み出す

前回までのところで、ローカルで空っぽのUbuntuを用意することができました。
おそらく、評価環境や商用環境など成果物のデプロイ先は、AWSなどで空っぽのOSを立てることからのスタートです。
Java自体の復習をしたいという狙いもありますが、同時に、成果物を動作させていくのに必要な内容を掴んでいきたいと思います。
これ以降のコマンドは、基本、立ち上げたUbuntuで打っていくことを想定しています。

shiba-hiro.hatenablog.com

shiba-hiro.hatenablog.com

Javaを動作させる

Javaとは何ぞや的な詳細は、ほかのところに譲ります。
Sun Microsystemsがリリースしたオブジェクト指向プログラミング言語で、 JVMと呼ばれるJava仮想マシン上で動作します。
仮想マシンをかませるところには、 "write once, run anywhere"の思想が埋まっています。

ということで、早速セットアップを進めます。

$ sudo apt update
$ sudo apt install -y default-jdk

$ export JAVA_HOME=/usr/lib/jvm/default-java
$ export PATH=$PATH:$JAVA_HOME/bin

今回は実際にJavaのソースを仮想マシン上で用意してコンパイル、実行するところまでやりたいので、JDK(Java Development Kit)をインストールします。
実行環境だけ必要であれば、JRE(Java Runtime Environment)だけあれば十分です。
JREには先に述べたJVMが含まれます。

# 実行させたいだけならJREのみでOK
$ sudo apt install -y default-jre

なお、JDKにはJREも内包されているので、重ねてインストールコマンドを叩く必要はありません。
ここまでで、java -version, javac -versionといったコマンドでバージョン確認ができるようになっています。

$ java -version
java version "1.7.0_151"
OpenJDK Runtime Environment (IcedTea 2.6.11) (7u151-2.6.11-2ubuntu0.14.04.1)
OpenJDK 64-Bit Server VM (build 24.151-b01, mixed mode)

$ javac -version
javac 1.7.0_151

ではHello World してしまいましょう。

$ echo 'public class Hello{ public static void main(String[] args){ System.out.println("Hello World"); } } ' > Hello.java

$ javac Hello.java
$ java Hello 
Hello World

Javaファイルを作成して、コンパイルして、実行する、という流れですね。

引数をつけたければ次のように行いましょう。

$ echo 'public class WithArg{ public static void main(String[] args){ System.out.println("See it, " + args[0]); } }' > WithArg.java

$ javac WithArg.java
$ java WithArg this-is-argument
See it, this-is-argument

Mavenを使う

ビルドツールを利用するのが普通だと思っているので、mavenを利用したプロジェクト作成から実行もやってみます。
(厳密には、mavenはもっと大きな役割を包含したものですが、便宜上そう呼称します)

https://maven.apache.org/index.html

Apache Maven is a software project management and comprehension tool.

# インストール
$ sudo apt install -y maven 

$ mvn -version
Apache Maven 3.0.5
Maven home: /usr/share/maven
Java version: 1.7.0_151, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-7-openjdk-amd64/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.13.0-141-generic", arch: "amd64", family: "unix"

# サンプルプロジェクトの作成
$ mvn archetype:generate \
>   -DarchetypeArtifactId=maven-archetype-quickstart \
>   -DinteractiveMode=false \
>   -DgroupId=com.sample \
>   -DartifactId=mvn-example

# 雛形の作成を確認
$ cd mvn-example/
$ ls -R
.:
pom.xml  src

./src:
main  test

./src/main:
java

./src/main/java:
com

./src/main/java/com:
sample

./src/main/java/com/sample:
App.java

./src/test:
java

./src/test/java:
com

./src/test/java/com:
sample

./src/test/java/com/sample:
AppTest.java

# コンパイル
$ mvn compile

# targetディレクトリ以下が作成されていることを確認
$ ls target/ -R
target/:
classes

target/classes:
com

target/classes/com:
sample

target/classes/com/sample:
App.class

# mavenで実行
$ mvn exec:java -Dexec.mainClass=com.sample.App
...
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 20.487s
[INFO] Finished at: Thu Feb 08 13:58:23 UTC 2018
[INFO] Final Memory: 8M/21M
[INFO] ------------------------------------------------------------------------


# もちろんjavaコマンドも利用可能
$ java -cp target/classes/ com.sample.App
Hello World!

雛形の作成から実行まで、スムーズに進められるようになりました。
おまけ的に、mvnコマンドに引数を設定する方法もメモしておきます。

# 引数を使うプログラムへ、App.javaを書き換え
$ echo 'package com.sample; public class App{ public static void main( String[] args ){ System.out.println("See it, " + args[0]); } }' > src/main/java/com/sample/App.java 

# 再度コンパイル、引数をつけて実行
$ mvn compile
$ mvn exec:java -Dexec.mainClass=com.sample.App -Dexec.args=mvn-sample-arg
...
See it, mvn-sample-arg
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.088s
[INFO] Finished at: Thu Feb 08 14:06:16 UTC 2018
[INFO] Final Memory: 7M/17M
[INFO] ------------------------------------------------------------------------

20180210追記;
Mavenについての説明記事を投稿しました。

shiba-hiro.hatenablog.com

空っぽの環境をつくるところから学習を始める(docker)

virtual boxが立ち上がらなくなった

前回はvagrantを使って実験用の環境をローカルに確保しようと試みました。

shiba-hiro.hatenablog.com

しかしながら問題が発生。
仕事だとOSにUbuntuを使っているんですが、あるときからvirtual boxで仮想マシンを立ち上げようとすると、PCがフリーズするという事象に見舞われるようになりました。
このままだと「空っぽの環境から云々」の趣旨を達成できなくなってしまうので、回避策的にDockerでUbuntuを立ち上げてみます。

まあ、ローカルマシンで確認のためにやってることなのでここで生じる差異については許されたい。。。
仮想化とかコンテナとかの話は、また別途ちゃんと記事に書きたいと思っています。
本日はひとまずセットアップ。

20180216追記;
このフリーズ事象は解決できました。

shiba-hiro.hatenablog.com

docker-ce のインストール

ホストOSへDockerをインストールします。
ここは公式ドキュメントがしっかりしていて、もうコマンドをコピペするだけでできてしまいます。

docs.docker.com

日本語のドキュメントもあるようですね。

http://docs.docker.jp/engine/installation/linux/docker-ce/ubuntu.html

# 必要な諸々のパッケージをインストール
$ sudo apt-get update
$ sudo apt-get install -y \
>    apt-transport-https \
>    ca-certificates \
>    curl \
>    software-properties-common

# GPG鍵の取得と設定
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# リポジトリの設定
$ sudo add-apt-repository \
>    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
>    $(lsb_release -cs) \
>    stable"

# dockerのインストール
$ sudo apt-get update
$ sudo apt-get install -y docker-ce

# サンプルイメージの起動
$ sudo docker run hello-world
...
Hello from Docker!
...

dockerでubuntuを立ち上げる

ではdockerでubuntuを立ち上げます。

$ sudo docker run -td --name ubuntu-on-docker ubuntu

イメージの取得もrunのタイミングで勝手にやってくれます。

-d は、バックグラウンドでdockerのプロセスを実行させることを命じるオプションです。
走らせたdockerには、それぞれフォアグラウンドで実行しているプロセスがあります。
(そのプロセス自体を走らせないと、コンテナが走りません)
そのプロセスを、ホストから見てバックグラウンドで実行させるためのコマンド、という感じでしょうか。

-tは擬似端末の割り当てです。これを入れてあげないとUbuntuのコンテナは走ってくれません。

--nameはコンテナ名の指定ですね。
いちいち生成されるidを使って操作するのはイケてないので、コンテナ名を付与して操作していくようにしましょう。

さて、やりたいことは「空っぽの環境の中でいろいろやる」ということです。
ここまでで、 vagrant upしたのと似たような状況になったのですが、vagrant sshに相当する操作が必要です。
そこで、次のコマンドを打って、コンテナの中に「入って」みます。

$ sudo docker exec -it ubuntu-on-docker /bin/bash

docker execはコンテナに特定のコマンドを実行させます。
このケースだと、bashを実行することで、vagrant ssh的な操作を実現します。

-tは先述のものと同様。
-iはコンテナ内の入力をホストの入力と結びつける役割を果たすもの(らしい)です。

なので、こうするとdockerのコンテナの中でbashコマンドが実行できます。

$ sudo docker exec -it ubuntu-on-docker /bin/bash

# docker コンテナ内へ操作が移る
root@5c930a81d61c:/# 

root@d36e655ec845:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr

root@d36e655ec845:/# cd ~

root@d36e655ec845:~# echo "hello world"
hello world

これでほぼやりたい準備は整ったんですが、ここからあとの操作をスムーズにやるために、sudoコマンドくらいは入れておきましょう。

root@d36e655ec845:~# apt-get update
root@d36e655ec845:~# apt-get install -y sudo
root@d36e655ec845:~# rm -rf /var/lib/apt/lists/*

これで、vagrantで立ち上げたときとの微妙な差異は、インストール等行って適宜解消していけます。
参考: https://github.com/tianon/docker-brew-ubuntu-core/issues/48

docker のコンテナから抜けるときはexitを叩けばOKです。

root@d36e655ec845:~# exit
exit

# ホストOSに操作が戻る
$ 

dockerコンテナに関して、本当にとりあえず知っておけばいいコマンドは下記くらいなものでしょうか。
docker run: コンテナの起動
docker stop: コンテナの停止
docker rm: コンテナの破棄。-fオプションの併用で起動中のコンテナでも破棄できる。
docker ps: コンテナの一覧表示。-aオプションの併用で停止中のコンテナも表示できる。
docker images: 手元にあるdockerイメージの一覧表示。
docker search: 入力したtermに応じてリポジトリ内のイメージを検索、表示。

ということで、次は本当に空っぽのUbuntu内でJavaを実行してみます。

なおプライベートで利用しているPCはMacbook airで、こちらは問題なくvagrantを利用した仮想マシンの起動が利用できています。
なのでこの先、ところどころ、docker上のubuntuなのかVM上のubuntuなのかで書き方読み方が変わる箇所が出てくるかと思うんですが、そこはなんか備忘録なんで許してください。

空っぽの環境をつくるところから学習を始める(vagrant)

ブログ化した発端

自分のキャッチアップ内容を、ネット上で閲覧できる情報としてアーカイブしたかったのです。
どこからでも見れて、かつ自分としては積み上がっていくのが実感できれば楽しそうだなーと。
基本的には備忘録を公開していくスタンスです。

初回でvagrantを扱う理由

webアプリつくる仕事をさせていただいてて、基本、大きいプロジェクトへの追加機能開発やバグ修正をメインにやってました。
が、最近ちょっとずつ仕事の幅がインフラやデリバリにも広がってきたのです。
で、安全な環境で実験する方法を覚えたり、空っぽのEC2からアプリの起動まで持っていくステップを理解したりしたく、まず空っぽの環境をつくるところから始めます。

ローカル(開発環境、あるいは用意された環境)ならできたのに、的なのに陥らないようにしたいんですね。

ubuntu仮想マシンで立ち上げる

ってことで、さくっと仮想環境を立ち上げます。
ホスト側でのインストール箇所以外はどのOSでも変わらんはず。

ボックスは公式から探します。
app.vagrantup.com

本ブログでは aptを使ってインストールしています。
Macユーザー、Windowsユーザーの方はそれぞれ下記の記事などを参考にしてインストールしましょう。

Vagrant+VirtualBoxのインストール(Mac) - Qiita

Windows10のVagrantで仮想環境 | あきらめずにwindowsで開発する。

では始めましょう。

$ sudo apt install virtualbox
$ sudo apt install vagrant

# 適当なディレクトリをローカルに作成
$ mkdir -p ~/SandBox/vagrant-practice/for-ubuntu
$ cd ~/SandBox/vagrant-practice/for-ubuntu/

# 起動
$ vagrant init ubuntu/trusty64
$ vagrant up

こんだけです。簡単ですね。
vagrant upしたときに、ローカルにボックスが見つからなければ、ボックスを取得するところからやってくれます。

(20180226追記;0226以降は基本的にubuntu/xenial64(16.04)ベースでの動作確認に変更しています)

時間がかかりますが、起動が完了するとサーバーを操作可能になります。

# ゲストOSへssh接続
$ vagrant ssh
Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 3.13.0-141-generic x86_64)
...
vagrant@vagrant-ubuntu-trusty-64:~$

# 簡単な操作を試してexit
vagrant@vagrant-ubuntu-trusty-64:~$ echo "Hello World"
Hello World
vagrant@vagrant-ubuntu-trusty-64:~$ exit
logout
Connection to 127.0.0.1 closed.

# ホストOSの操作に戻る
$ 

とりあえずこれ覚えておけばいいっしょなコマンドは下記あたりでしょうか。

vagrant status : ステータス確認
vagrant up : ゼロからの起動、halt後の再起動
vagrant halt : 停止
vagrant destroy : 削除
vagrant ssh : 接続

次は、こうして立ち上がった空っぽのサーバーで、ひとまずJavaのプログラムを走らせてみます。

20180216追記;
Ubuntu上でvagrant upしたときにフリーズしてしまう事象に一時見舞われましたが、解消しました。

shiba-hiro.hatenablog.com