焼売飯店

GoとかTS、JSとか

Rubyでdotenv的なやつを小さなスクリプトで実装してみる

手元のプロジェクトで環境変数をファイルから読み込む必要が出てきて、dotenvが欲しくなったのですが、ただそれだけのために依存関係増やすのもなぁ…と思ったので、自分で書いてみることにしました。

コード

envloader.rb

class Envloader
  class << self
    def load(filepath = File.expand_path('../.env', __FILE__))
      File.open(filepath) do |f|
        f.each_line do |line|
          if line =~ /\=/
            key, value = line.split('=').map(&:strip)
            value = value.match(/[\'\"]?([^\'\"]+)[\'\"]?/)[0] # 引用符を削除
            ENV[key] = value
          end
        end
      end
    end
  end
end

長さは15行です。(短い!)

使い方

1. .envファイルを作る

設定したい環境変数を含んだ.envファイルをenvloaderと同じディレクトリに置きます

.env

USERNAME=username
PASSWORD="secret" # 引用符も使える

2.使いたいスクリプト中で読み込む

main.rb

require './envloader'

Envloader.load

ENV['USERNAME'] #=> "username"

実装の方針

やりたかったのは以下の内容でした。

  1. .envにgitで追跡したくない情報を入れておいてスクリプト中で読み込みたい
  2. .envの書き方は hoge=huga もしくは、右辺を ''"" でくくったやつだけで、一つにつき一行使う
  3. 読み込んだ環境変数はENVに入るようにする

これに加えて、.envファイルのパスを指定できる機能もついてますが、流れで入れただけなので恐らくそこまで使わないかと思います…。

追加実装するなら、プロジェクトルートの自動認識とかでしょうか。

ここ最近は、こんな感じの細々したスクリプトを書いて楽しく過ごしています(笑

ドットでチェーンして値を追加していけるHashを実装してみた

手抜き実装なので、まだ色々足りて無さそうな感じがしますが、一旦の目的までは達成出来ています。

名前はChainableHashとしてみました。

コード

class ChainableHash < Hash
  def method_missing(name, *args, &block)  
    if name[-1] === '='
      self[name[0..-2].to_sym] = args.first
    elsif self.has_key?(name.to_sym)
      self[name.to_sym]
    else
      self[name.to_sym] = self.class.new
    end  
  end  
end

使い方

config = ChainableHash.new
config.environment = 'development' # environmentキーと値の追加
p config
# => {:environment=>"development"}

config.capacity.max = 10 # capacityを定義していなくてもそのままmaxまで指定できる
config.capacity.min = 5  # minキーの追加も簡単に出来る
p config
# => {:environment=>"development", :capacity=>{:max=>10, :min=>5}}

ポイント

else
  self[name.to_sym] = self.class.new
end  

ここの部分で、キーが存在しなかった場合にChainableHashのインスタンスを値としてセットするようにしています。 このおかげでチェーンが可能になっています。

Docker HubにPushしたイメージをAmazon ECSにデプロイしてみる

Docker HubにイメージをPushしてみる - 焼売飯店の続きです。

かなりアッサリ出来ました。

手順

1.AWSコンソールからECSを選択

f:id:f_syumai:20170114122335p:plain これら2つをガイド付きで実行できるようになっています。

ただし、今回はこのどちらも使わない(ECR使わないし、サンプルアプリも不要なので)ので、キャンセルを押して次に進みます。

2.タスクの登録

f:id:f_syumai:20170114122638p:plain ECSでイメージを実行するには、タスクの登録が必要になります。 実行するイメージを登録(複数可)したり、リンクさせたりなどの設定をこちらで行います。 Docker Composeで言うdocker-compose.ymlみたいな物だと考えるとわかりやすい気がします。 こちらでは、コンテナのメモリ、CPU数も設定できます。

f:id:f_syumai:20170114122952p:plain イメージには、前回Docker HubにPushしたsyumai/sinatra-alpine:0.1.0を指定しています。 シンプルなSinatraアプリなので、メモリ128MB、CPU数1で登録しました。

3.クラスターの作成

タスクを実行するためのクラスターを作ります。 こちらは、コンテナを実行するインスタンスの数、種類、スケーリング方法などについて設定を行います。 試験的にデプロイしたいだけなので、インスタンス数1のt2.microで作りました。

セキュリティグループに関しては、インバウンドで80番を受け付けるように設定します。

4.タスクの実行

こちらのクラスタで、先ほど作ったsinatra-alpineのタスクを実行してみます。 クラスタの作成と同時にコンテナ実行用のEC2が一つ立ち上がるのですが、2,3分ほどかかるので、少し待ってから実行する必要があります。

f:id:f_syumai:20170114123516p:plain

こちらも、あっさり動きました!

まとめ

実行するだけなら本当に簡単に出来てしまいました。 本番環境で使うとなると、今回タスクの実行で起動したものをサービス(今回は使いませんでした)で起動するようにしたり、複数コンテナを連携させたり、ELB(ロードバランサ)を追加してスケーリングに対応できるようにしたりする事になるかと思います。 この辺りは追い追いやっていこうかと思います。

Docker HubにイメージをPushしてみる

SinatraアプリのHello world on Docker with Alpineの続きです。

前書き

前回作ったSinatraアプリのコンテナを、Amazon EC2 Container Services (以下、ECS) にデプロイ出来るようにしたいと思います。 ECSから自分のイメージを使えるようにするためには、どこかしらのレジストリにイメージを配置する必要があります。

イメージの置き場所について

ECSから読めればどこでも良い(自前のリポジトリも可?)ですが、 最も一般的なのはAmazon本家のEC2 Container Registry(ECR)です。

今回は、手軽に無料で使えるDocker Hubを使いますが、無料では公開リポジトリしか置けないですし、 ECRにイメージを置いておけば、同一リージョン内からのイメージのpull、すなわちEC2へのデプロイで料金が発生しないので、ECSを使う上では非常にお得です。

ECRの料金は次の項目で発生します。

  • ストレージ料金 => 0.10 USD/GB/月
  • イン(Push) => 0円
  • アウト(同一リージョン外からのPull) => 最初の1GB無料、それ以降 0.140 USD/GB (月に10TBまでの場合)

気軽に試す分には、維持費のかからないDocker Hubでもいいかなと思います。

手順

1.Docker Hubにリポジトリを作る

今回は、syumai/sinatra-alpineとしました。

f:id:f_syumai:20170113023005p:plain

2.タグ付きでビルド

docker build -t syumai/sinatra-alpine:0.1.0 ./

3.Docker Hubにログイン

docker login
(ユーザー名、パスワードの入力)

4.Pushする

docker push syumai/sinatra-alpine:0.1.0

Push完了!

Pushしたイメージを確認する

Pushしたイメージは、Docker Hub上の自分のページに、あれ、見つからない…。 でもdocker pull syumai/sinatra-alpine:0.1.0は動くので、Pushは成功しているっぽいです。 どっか間違ったのか時間差があるのでしょうか…。また明日確認してみます。

次回は、こちらのアップロードしたイメージ or ECRを使ってECSにデプロイしてみます。

追記(2017/1/14)

6時間後くらいに見たら、ちゃんとDocker Hubのページに反映されていました。

SinatraアプリのHello world on Docker with Alpine

RailsのデプロイをDockerで行いたかったのですが、 色々躓いてしまったので、一度シンプルにSinatraで動く物から作って、一歩ずつ確認していこうと思います。

今回は、完全にHello worldだけなので超お手軽構成です。

コードはこちら https://github.com/syumai/sinatra-alpine

ファイルの中身

Gemfile

source 'https://rubygems.org'

gem 'sinatra', '~> 1.4.7'

アプリ本体

0.0.0.0にホストをバインドしないと外からアクセス出来ないので、コード内で行っています。 ポート番号も同様にコード内で指定しました。(指定しなかった場合のポート番号は4567になります)

この二行を省略して、Dockerfile内で CMD ["ruby", "app.rb", "-o", "0.0.0.0", "-p", "80"] としても同様の動作となります。 どちらを選ぶかはお好みで。

require 'sinatra'
set :bind, '0.0.0.0'
set :port, 80
get '/' do
  'Hello, world!'
end

Dockerfile

ベースイメージはRuby 2.4.0(Alpine)です。 ベースイメージのサイズは55.82MBでした(軽い!)

追加のパッケージも必要無しです。さすがSinatraと言ったところですね…。

FROM ruby:2.4.0-alpine

RUN apk update && apk upgrade
WORKDIR /app
COPY Gemfile .
RUN bundle install && bundle clean
COPY . /app
EXPOSE 80
CMD ["ruby", "app.rb"]

ビルドして動かす

docker build -t sinatra-alpine .
docker run -p 80:80 sinatra-alpine -d
curl localhost #=> 'Hello, world!'

ここまでアッサリ動いてくれました。

ビルド後のイメージサイズは72.07MBとなっています。ベースイメージから17MB増でした。 (.dockerignoreで.gitディレクトリはイメージに含まないようにしてあります。)

次は、こちらのイメージをAmazon EC2 Container Service (ECS) or Google Container Engine (GKE)で動かしてみたいと思います。