stks56's tech blog

Keep it simple, stupid.

ログファイルからをバッチ処理を行う非コンテナアプリケーションをECSでサイドカーパターンとService Discoveryを使ってコンテナ内ロギングをする

概要

Dockerコンテナでアプリケーション構築するメリットは皆さんご周知ではあると思いますが、EC2でアプリケーションを公開していてECSなどのコンテナ基盤に乗り換えた時に、EBSやEFS等のlinuxファイルシステムに依存している部分をコンテナ上でどうするかという問題に直面します。

今回はその問題のうち、アクセスログファイル等を集計してDBに入れたいというありがちな問題をService DiscoveryとFluentdコンテナのサイドカーパターンで解決する手法を実践します。

そもそもなぜコンテナ内のファイルシステムに保持してはいけないか

Dockerfile 記述のベスト・プラクティスによると

Dockerfile が定義するイメージによって生成されるコンテナは、できる限り "はかないもの"(ephemeral)と考えておくべきです。 "はかない" という語を使うのは、コンテナが停止、破棄されて、すぐに新たなものが作り出されるからです。 最小限の構成や設定があれば稼動できます。

とあり、更にGCPコンテナ運用のベストプラクティスによると、

初めてコンテナを試している場合、従来のサーバーのようにコンテナを扱わないでください。たとえば、実行中のコンテナ内でアプリケーションを更新したり、脆弱性が発生したときに実行中のコンテナにパッチを適用したりしたくなるかもしれません。

コンテナは基本的に、このような処理に対応するように設計されていません。コンテナは、ステートレスかつ不変になるように設計されています。

コンテナは基本的に、このような処理に対応するように設計されていません。コンテナは、ステートレスかつ不変になるように設計されています。 とあり、コンテナ内では状態を持たない = ステートレス であることがかなり推奨されています。 コンテナ内で作られたファイルをコンテナ内で持ってしまうと、コンテナが破棄され次世代のコンテナが立ち上がった際に生成したファイルが無いので実質欠損するという事態が起こりえます。 これを回避するためにデータストアに格納することが推奨されていますが、今回はDockerの永続化Volumeを使ってデータを退避させます。

コンテナ基盤を使って運用するときの普通のロギング

ECS、kubernetesなどのコンテナ基盤を使う場合、Dockerの標準ログドライバーを使うことが推奨されています。 主に、docker logskubectl logsで見れる標準出力のログをどこかに集約させるという手法です。 大体各種クラウドにはコンテナ基盤に付随してロギングを集約させるマネージドサービスが搭載されていると思うのでそれを使ってデータストアに入れるパターが王道だと思います。 ただ、これらはアプリケーション側でログ出力の整備を行わなければ成立しません。今回はその整備にコストがかかる場合、既存のロギングシステムを残しつつ、コンテナ環境でどう再現するかの手法です。

構成図

f:id:stks56:20201207210426p:plain

コンポーネント

App Serviceの構築

メインのアプリケーションコンテナは複数コンテナ存在し、Fluentdのログコンテナは単一コンテナとしてログを集約させます。 App Serviceは通常通りnginx containerからunix socketsを介してapp containerに接続します。これに関してはこちらの記事を参照して構築していきます。 https://qiita.com/na-o-ys/items/1a863419e1f6c3063ace App Serviceに関してはもう構築できている前提で話を進めていきます。

Log Serviceの構築

Log Serviceですがこちらは別サービスになっているのでscalingはせず、単一サービスとして構築します。 まず、Log Service用のタスクはport mappingは指定せず、Dockerfileでコンテナ側でのみportを開いてください。

"portMappings": [],
"networkMode": "awsvpc",

単一サービスですのでタスクの数は1、最小ヘルス率は0、最大率は100としてタスクの重複を許さないようにします。

f:id:stks56:20201207210635p:plain

それと、今回はタスクのnetworkmodeはawsvpcに設定します。

続いてサービス検出の設定ですが、名前空間名は好みで変えても良いですが、慣習としてxxx.local xxx.internalという名前空間が使われやすいです。 サービスの検出名も好みで良いですが、分かりやすくfluentdのコンテナ名にします。

f:id:stks56:20201207211159p:plain

private DNSレコードは今回はAレコードを作成するようにします。

f:id:stks56:20201207211308p:plain

注意点としてタスクのnetworkmodeがawsvpcじゃない場合Aレコードが作成できないと思います。 代替としてSRVレコードが用意されていますがこちらは動的ポートマッピングを使用する為、既存のログプラグインで対応できない可能性があります。 awsvpcはENIをアタッチされてるのでAレコードからportを指定して接続ができるようになります。 ENIをアタッチする関係上インスタンスを制限されるデメリットもありますが、ネットワークパフォーマンスの観点からもECS全体でawsvpcが推奨されます。

以上でサービスを構築するとService Discoveryによるprivate DNSでのコンテナアクセスが可能になります。

f:id:stks56:20201207211003p:plain

タスク上でport mappingせずともdnsとportを指定して通信ができるようになったかと思います

root@21454022290b:/# dig fluentd.local

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.amzn2.0.4 <<>> fluentd.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;fluentd.local. IN  A

;; ANSWER SECTION:
fluentd.local. 10   IN  A   172.31.0.54

;; Query time: 1 msec
;; SERVER: 172.31.0.2#53(172.31.0.2)
;; WHEN: 木  7月 09 20:15:59 UTC 2020
;; MSG SIZE  rcvd: 60

ログプラグインでもportを指定して接続ができるようになっているかと思います。

host: fluentd.local
port: 24224

これでログ集約サーバーの出来上がりです。

サイドカーパターンでログファイルを処理する

fluentdにより生成されたログファイルを用いてbatch containerで処理したい場合、サイドカーパターンでvolumeを共有して永続化する必要があります。

まずfluentdのDockerfileでvolumeのpathを指定してあげます。

VOLUME /fluentd/log/xxx

注意点はコンテナ内の実行ユーザーによってはvolumeディレクトリに書き込めない場合があるので、適宜書き込み権限を追加してください。

続いて、batchをサイドカーパターンでvolume共有したタスクを用意します。

[
  {
    "name": "fluentd",
    "image": "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx-fluentd:e6767fd9",
    "cpu": 400,
    "memoryReservation": 400,
    "essential": true
  },
  {
    "name": "batch",
    "image": "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx-batch:e6767fd9",
    "cpu": 400,
    "memoryReservation": 400,
    "essential": true,
    "volumesFrom": [
      {
        "readOnly": false,
        "sourceContainer": "fluentd"
      }
    ],
    "dependsOn": [
      {
        "containerName": "fluentd",
        "condition": "START"
      }
    ]
  }
]

ポイントはvolumesFromを指定してあげて書き込み処理をしたい場合はreadOnly: falseにすることです。 念のため、もしfluentd containerの起動が遅れた状態でにバッチ処理が走らないようにdependsOnで起動を待つ指定もしておきます。 また、batchコンテナ内のユーザーにも書き込み権限を追加するようにしてください。

今回自分が実践したbatchはcronで回すタイプだったのでcron -fで定期実行していますが、何かしらのトリガーで無事ログファイルのbatch処理が正常に走れば完成です。

オブジェクト指向設計実践ガイドを読んだ所感

Sandi Metsのオブジェクト指向設計実践ガイドを読みました。

翻訳された書籍なので英文特有の言い回しだったり、冗長的に思える文もあったのですがじっくり理解しながら完読しました。

本文の構成は最初は一つのクラス単位の解説から始まっていき、章を進めていくにごとに他のクラスが登場したりして順序よく段階的に進められていきます。

最初は単一責任について述べ、その次にクラス間の連携をした際に発生する依存性をどうやって管理するかであったり、そのあとはクラス内のインターフェースの管理方法だったり、継承を用いたデザインパターンを紹介したり等、随所に設計原則やデザインパターンの解説が埋め込まれています。

原則やデザインパターンから実装を説明するのでは無く実装から原則やデザインパターンに当てはめていくといった文章構成になっています。(原則の名前すら出てこないで解説される時もあるので逆に原則を説明したいといった時は他の書籍を読んで把握していくといいと思います)

僕は一番関心を持ったのはRubyの動的型付けを生かしたダックタイピングによる依存関係の管理(ポリモーフィズム)と継承の際のテンプレートデザインパターンです。

オブジェクト指向による強力な設計が記載されてる反面、これを上手くRailsのプロジェクトに全て適応出来るかどうかと言うのはちょっと一発本番では難しいと思ってます。もう少し実際に程よく大きなRailsプロジェクトのコードを書くときに少しずつ実践したいです。

正直、今Rubyを仕事で使っている人の中でも上手く設計原則やデザインパターンを完璧に扱えているかと言うとそんな気はしないのでやっぱりこういう書籍を読んで自主的に動いていかないとダメですね。

AWSでwebサービスを構築する上で最低限知っておきたいネットワークの基礎知識

AWSではネットワークの知識は欠かせません。

インターネットで世界基準で定められてる通信規約としてOSI参照モデルTCP/IPプロトコル・スイートという規約があり、これを網羅して学習すればPC間でどうやって通信しているのか、ネットワークの構造は理解できます。

今回はTCP/IPプロトコル・スイートの中でwebサービスを構築するにあたり、知っておかないと苦労するものを説明します。

OSI参照モデルTCP/IPプロトコル・スイートの詳しい解説はこの記事ではしませんが、その中で何を主軸に置いて勉強すればいいのかは分かるかと思います。

f:id:stks56:20190710233436p:plain

オレンジ色に塗られている部分がAWSで特に意識する部分です。

TCP/IPのアプリケーション層ではWebサーバーやWebプログラム担当範囲でAWSに任せる担当ではありません。(唯一どのプロトコルをWebサーバーに投げるかというのを意識する必要があるのでプロトコル名だけは知る必要があります)

ネットワークインターフェース層はハードウェアに責任を負わせる部分でAWS側でよしなになってくれるので知っておく必要はありません。

AWSのサービス毎に説明します。

VPC

AWSアカウント内に自分だけのネットワークを構築するサービスです。 主にインターネット層での知識が必要になります。 VPC本体やサブネットの領域の割り当て、ルートテーブル、インターネットゲートウェイ・NATゲートウェイのルーティングなど、一番ネットワークの知識が要求されるサービスです。 IPとサブネットマスクの知識、CIDR表記等を知らないと構築するのは難しいです。

Route53

DNSの登録や管理をするサービスです。 こちらも主にインターネット層の知識が必要になります。 DNSの登録、DNSレコードセット、DNSのマウントなどIPの知識と共に、DNSレコードの意味を理解してないと難しいかと思います。

ELB、ALB

ロードバランサー(負荷分散システム)を提供するサービスです。 Webサービスの場合ALB(Application Load Balancer)を使うことになると思います。 こちらはインターネット層とトランスポート層プロトコル名の理解が必要になります。 リスナーからターゲットのポートを指定したりなど特にトランスポート層の知識が必要になります。 (ECSなどのコンテナへ接続したい場合、動的ポートマッピングという少し特殊な設定をしたりします)

EC2、RDSなどの各インスタンス

ネットワーク内でいうPC本体を提供するサービスです。 RDSはサービスとして抽象化されていますが実体はPC内のソフトウェアなのでIPアドレス自体は持っています。 webサーバー等でALBからのリクエストを受け付けるのでトランスポート層プロトコル名の理解が必要になります。 SSHインスタンスに接続したりもします。

以上です。

AWSのサービスを使うにあたり、ネットワークの知識が足りてないと構築に手も足も出ない反面、知っているとすんなりサービスを理解できたりするのでやはり何かネットワークを体系的に学べる書籍等で勉強してから構築した方がいいかと思います。

0からネットワークを理解するなら以下のスラスラわかるネットワーク&TCP/IPのきほんがイラスト付きでわかりやすいと思うので是非読んでみてください。(自分はこれだけでは物足りなかった&もっと深掘りをしたいと思ったので次回はマスタリングTCP/IPを購入予定です)

スラスラわかるネットワーク&TCP/IPのきほん 第2版

スラスラわかるネットワーク&TCP/IPのきほん 第2版

マスタリングTCP/IP 入門編 第5版

マスタリングTCP/IP 入門編 第5版

Rubyチートシート

先日、Rubyの復習や深掘りをしたかった為、「プロを目指す人のためのRuby入門」を読み返しました。

読み返して見ると初回に読んだ時では理解し難い部分がすんなり理解できたり、あまり触れることが無く忘れていたメソッドや記法を改めて再認識できたので読み返すのもありですね。

ただ読み返すだけだとつまらないので自分なりに重要なところを纏めたチートシートを作って見ました。

そのまま引用してる部分も多いですが、そこはご容赦ください。

ファイルとして保存してあるので今後はこれで修正改善しながら見たらすぐ思い返せるようなノートに仕上げていきたいですね。

「プロを目指す人のためのRuby入門」はRubyを学ぶなら読んで損は無い良書なのでまだ読んでない方は是非読んでみてください。

プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで Software Design plus 伊藤 淳一 https://www.amazon.co.jp/dp/B077Q8BXHC/ref=cm_sw_r_tw_dp_U_x_Mq0iDbY2N9NGT

全てがオブジェクト
  文字列、配列はもちろん、数値、nil、true、falseもオブジェクト

メソッドの呼び出し
  オブジェクトのメソッドを呼び出し
    object.method(引数1, 引数2, 引数3)
  引数のカッコ省略
    object.method 引数1, 引数2, 引数3
  引数なし
    object.method

文の区切り
  改行、セミコロンが文の区切り。
    1.to_s
    nil.to_s; 10.to_s(16)
  カッコが締められてない、バックスラッシュで文はまだ続く
    10.to_s(
    16
    )

    10.to_x \
    16

リテラル
  数値
    123
  文字列
    'Hello' %q(Hello) %s(Hello)←シンボル
  文字列(変数展開可)
    "Hello" %(Hello)
  シンボル
   :hello :'hello' :'123'
  配列
    [1, 2, 3] %w(1 2 3) %W(1 2 3)←変数展開可能
  配列(シンボル)
    [:Ruby, :Python, :PHP] %i(Ruby Python PHP)
  ハッシュ
    {'japan' => 'yen', 'us' => 'dollar', 'india' => 'rupee'}
  ハッシュ(シンボル)
    {japan: :yen, us: :dollar, india: :rupee}
  正規表現
    /[\w\d]+/

バックスラッシュでエスケープ

変数宣言と配列操作
  変数宣言
    s = 'hello'
  同時代入
    a, b = 1, 2
  配列へ追加
    ary << 3
  配列展開
   [1, 2, *ary] #=> [1, 2, 1, 2, 3]

変数の種類
  ローカル変数
    name
    スコープ内のみ参照可能
  定数
    NAME
    クラス内だとどこでも参照可能
    クラス外は
    Class::NAME Module::NAME
    の形で参照可能
    Rubyでは書き換え可能なので.freezeメソッドを使うかマジックコメントを使う
  インスタンス変数
    @name
    インスタンスメソッドのみ参照可能
  クラスインスタンス変数
    @name
    クラス直下とクラスメソッドのみのみ参照可能
  クラス変数
    @@name
    クラス全体とそのクラスの親子メソッド全てのみ参照可能
  グローバル変数
    $name
    どこからでも参照可能
  組み込み変数、定数
    $
    Rubyが定義した変数、元から持っている定数
  環境変数
    ENV['name']
    OS、マシンが持っている環境変数

命名規則
  スネークケース
    変数名、メソッド名、ファイル名
  キャメルケース
    クラス名、モジュール名
  すべて大文字
    定数

比較演算子
  ==, !=, <, >, <=, >=

真偽値と条件分岐
  基本形if
    if 条件式 && 条件式 || 条件式
      trueの処理
    elsif 条件式
      trueの処理
    else
      falseの処理
    end

  一行で書く(Guard句)
    trueの処理 if 条件式

  三項演算子
    条件式 ? trueの処理 : falseの処理

  true falseだけ返したいとき
    !!条件式

  ifの逆、unless
    unless 条件式
      falseの処理
    end

  case文
    case 対象
    when 条件
      trueの処理
    end

範囲(Range)
  1..5
  'a'..'e'
  ...を使うと5が範囲に含まれない(1以上5未満)
  1...5

繰り返し処理
  each map select find inject 配列に対して
  times upto downto step 数値に対して

  ループ処理
  loop do #breakされるまで
    break #処理から抜け出す
  end

  while 条件式 #真だったら実行
    next #次の周回へ
    redo #今の周回をやり直す
  end

  until 条件式 #偽だったら続行
  end

  for 変数 in 配列やハッシュ #eachメソッドと一緒 使わぬように
  end

  大域脱出
  catch :done do
    throw :done
  end

クラス
  サンプル
    class User
      DEFAULT_PRICE = 100
      @pet = 'cat'

      attr_accessor :name, :price

      def initialize(name, price) #newの際にこれが呼ばれる デフォルトでprivateメソッド
        # 初期化処理
        @name = name #newの引数をインスタンス変数に代入
        @price = price
      end

      def hello #インスタンスメソッド
        "Hello, I am #{@name}."
      end

      def self.this_class #クラスメソッド
        "Userクラス"
      end
    end

  クラスの定義
    class User

    end

  オブジェクトの作成
    user = User.new('Alice', 100)

  インスタンスメソッドの定義
    def hello #インスタンスメソッド
      "Hello, I am #{@name}."
    end
    以上のように普通にメソッドの定義を行うとインスタンスメソッドになる

  インスタンスメソッドの呼び出し
    user.hello #=> "Hello, I am Alice."

  アクセサメソッド
    attr_accessorがアクセサメソッド、以下の二つを併せ持つ
      attr_writer
        セッターメソッド
          def name=(value)
            @name = value
          end

          user.name = 'Alice'
          以上のようにインスタンス変数に代入するメソッド
      attr_reader
        ゲッターメソッド
          def name
            @name
          end

          user.name #=> "Alice"
          以上のようにインスタンス変数を外部から参照するメソッド

  クラスメソッドの定義
    def self.this_class
      "Userクラス"
    end
    メソッド名にself.をつけるとクラスメソッドになる

    class User

      class << self
        # ここにクラスメソッドを定義(selfなし)
      end
    end
    以上のようにすることでself.をつけなくても定義が可能

  クラスメソッドの呼び出し
    User.this_class
    クラス名から呼びだす

  メソッド名の表記法
    インスタンスメソッドの表記法
      "User#hello"
    クラスメソッドの表記法
      "User.this_class" "User::this_class"

  定数の呼び出し
    クラス内であれば
      DEFAULT_PRICE
    クラス外であれば
      User::DEFAULT_PRICE

  selfキーワード
    クラス直下だとクラス自身
      User
    クラスメソッド内もクラス自身
      User
    インスタンスメソッド内だとそのクラスのインスタンスを表す
       <User:0x007fbffc094070>
      なのでインスタンスメソッド内ではセッターメソッドの呼び出し時のみつける

  クラス内の定数やメソッド呼び出し
    クラス直下から呼び出す場合
      定数
        DEFAULT_PRICE
      クラスメソッド
        self.this_class
      インスタンスメソッド
        呼び出せない
      クラスインスタンス変数
        @pet
    クラスメソッドから呼び出す場合
      定数
        DEFAULT_PRICE
      クラスメソッド
        self.this_class
      インスタンスメソッド
        呼び出せない
      クラスインスタンス変数
        @pet
    インスタンスメソッドから呼び出す場合
      定数
        DEFAULT_PRICE
      クラスメソッド
        User.this_class
      インスタンスメソッド
        hello
      クラスインスタンス変数
        呼び出せない

  クラスの継承
    class DVD < Product; end
    継承する場合is_a関係を意識する(AはBである)

  メソッドのオーバーライド
    スーパークラスのメソッドと同名のメソッドを定義することでオーバーライドできる
      superでスーパークラスの同盟のメソッドを呼び出せる

  メソッドの公開レベル
    キーワードを記述した下のメソッドに適用される

    public
      クラス内外から自由に呼び出せる
      initialize以外はデフォルトでpublic
    private
      クラス外からでは呼び出せない(レシーバ付きで呼び出せない)
      インスタンスメソッドはprivate :hello のように引数を渡せば後から設定可能
        クラスメソッドをprivateにしたい場合
          ①class << self 構文の中でprivateキーワードを使う
          ②private_class_method :hello 構文を使い設定(定義した後)
    protected
      クラス外からでは呼び出せないがクラス内であればレシーバ付きで呼び出せる
        protected :hello で後から設定可能

  エイリアスメソッド
    メソッドを定義した後、
      alias greeeting hello
      alias エイリアスにしたいメソッド 元のメソッド

  メソッドの削除
    undef hello

    特異メソッド
      指定したオブジェクトのみ呼び出せるメソッド
        def alice.shuffle
          # 処理
        end

モジュール
  モジュールの定義
    module Greeter; end

  モジュールの活用方法5つ
    ①インスタンスメソッドとしてミックスイン
    ②クラスメソッドとしてミックスイン
    ③名前空間
    ④関数や定数の集まりとして定義
    ⑤設定値の保持

  ①②ミックスイン
    module Greeter
      def hello
        'hello'
      end
    end

    インスタンスメソッドとしてミックスイン
      include Greeter
    特異メソッド(クラスメソッド)としてミックスイン
      extend Greeter
    メソッドの優先度を変えつつインスタンスメソッドとしてミックスイン
      prepend Greeter
      ミックスイン先の同名メソッドよりモジュールのメソッドが先に呼ばれるようになる
      メソッドの探索順位はancestorsメソッドで確認

    クラスと同じでprivate, protectedその他機能が有効

  ③名前空間
    module Clock
      class Second; end
    end

    もしくは

    module Clock; end

    class Clock::Second; end

      モジュール外から該当モジュールにに属するクラスの呼び出し
        Clock::Second.new
      モジュール内から
        Second.new
      モジュールに属してないトップレベルの同名クラスの呼び出し
        ::Second.new

  ④関数や定数の集まりとして定義
    module Loggable
      def self.log
        # 処理
      end
    end

    もしくは

    module Loggable
      class << self
        def log
          # 処理
        end
      end
    end

    モジュールの特異メソッドとして呼び出し
      Loggable.log

    モジュールの特異メソッドとして機能させつつ、ミックスインとしても使う
    module Loggable
      class << self
        def log
          # 処理
        end
      end
      module_fuction :log
    end


    モジュールに定数を定義
      module Loggable
        PREFIX = '[LOG]'.freeze
      end

  ⑤設定値の保持
    gemなど、設定値を一元管理する為にだけ使用するパターン
      module AwesomeApi
        @base_url = ''
        @debug_mode = false

        class << self
          attr_accessor :base_url, :debug_mode
        end
      end

    設定値の保存
      AwesomeApi.base_url = 'http://example.com'
      AwesomeApi.debug_mode = true

    設定値の呼び出し
      AwesomeApi.base_url
      AwesomeApi.debug_mode

例外処理
  例外の補足
    begin
      例外が発生するかもしれない処理
    rescue => e
      retry #処理をやり直す
      例外が起こった場合の処理
    else
      例外が発生しなかった場合の処理
    ensure
      例外が有無に関わらず実行する処理
    end

    rescue => eとすることでeに例外オブジェクトが入る
      e.class
        例外のクラス
      e.message
        例外のメッセージ
      e.callback
        例外が起こった時のバックトレース

      処理をやり直す
        retry
        無限ループに注意

    else
      あまり使われない
      理由としてはbegin内でカバーできる為
      ただし、else節は補足されないという点が違う

    ensure
      終了する前に必ず実行したい処理がある時に使う
      returnは絶対に使わないこと(例外を握り潰す)

    メソッド内ではbegin, endを省略可能
      def method
        例外が発生するかもしれない処理
      rescue => e
        retry #処理をやり直す
        例外が起こった場合の処理
      end

  例外を発生させる
    raise ArgumentError, "引数が無効です #{argument}"
      第一引数に例外クラス、第二引数にメッセージ

  独自の例外クラスを作る
    class NoCountryError < StandardError

    end

    必ず、継承元をStandardErrorにすること

ECSでEC2インスタンスを選択する場合はEC2インスタンスタイプに配慮すること

ECSではFargateとEC2の二つの起動タイプがある。

FaegateはともかくとしてEC2を選択する場合はEC2インスタンスタイプのCPU及びメモリといったリソースを十分考慮しながら設計する必要がある。

ECSではタスク定義で動かすコンテナが使えるメモリを割り当てられるが(ハード/ソフト制限)、ここで定義されるリソースはEC2インスタンスのリソースの範囲を超えてはならない。

こちらはAWS EC2インスタンスタイプの一例である。 f:id:stks56:20190525184102p:plain

今回は無料枠で使えるt2.microインスタンスタイプで説明する。

t2.microインスタンスタイプはメモリが1GiBである(1GiB = 1024MiB)

つまりコンテナには1024MiBを超えるメモリを割くことができない。

じゃあ1024MiB割り当てればいいと思うかもしれないが、ローリングアップデートの事も考えなければならない。

ローリングアップデートでは2つのタスクが共存する瞬間があるのでその間2倍のメモリを使うことになる。

なので二分の一で512MiG割り当てればいいと思うかもしれないがこれも違く、ギリギリのメモリリソースでタスクを実行しようとすると、安定せずデプロイが失敗する可能性がある。

なのである程度余裕を持たせて設計する必要がある。

自分はt2.microインスタンスに300MiGのコンテナを割り当てることでデプロイが安定した。

t2.microインスタンス(1024MiG) > コンテナ(300MiG) + ローリングアップデート用のコンテナ(300MiG) + 余白

これのタチの悪いところはわかりやすくアナウンスされないところだ。

run task cli を叩いてもRESOURCE:MEMORYしかアナウンスされずどこのメモリが足りないかわからなかった。

Dockerのコマンドを理解する

概要

Dockerのコマンドが何をしているかを自分なりに簡潔に解説します。

対象者

  • Dockerの概念はなんとなく理解できたけど自分で環境構築ができない方
  • とりあえずコマンドは知ってるけど詳しく何をやっているかわからない方

なぜ書こうと思ったのか

自分はDockerで開発環境で構築できたが学習の際、技術記事を見て回ったがDockerの概念を説明する記事やDockerで開発環境を構築するチュートリアル的な記事は数あれど、具体的にDockerのコマンドに注力して解説してる記事が少なかったので初学者の助けになればと思い寄稿しようと思いました。 また、難しい概念抜きにしてをコマンド一つ一つを簡潔に理解することによって学習が捗ったので今回は「簡潔に」をテーマとして書いていきます。

注意点

解説記事によってはdocker buildだったりdocker runとして解説している記事がありますが、厳密にはdocker image build docker container runです。

imagecontainerを記載することで何に対して実行しているコマンドなのかを理解できる為、今回は省略せずに解説します。

解説

docker images

現在自分のマシンが持っているDockerイメージの一覧を表示する

docker image pull (イメージ名)

DockerHubにあるイメージを自分のマシン上にダウンロードする

docker image build (-t イメージ名:タグ名) (Dockerfileのディレクトリのパス)

Dockerfileを元にイメージを作成する -tオプションを付けることで自分の好きな名前をつけられます さらに-fオプションを付けることでファイル名がDockerfileでないファイルを指定できます。 (-f ファイル名)とすることでそのファイル名を指定します。

docker container run

イメージからコンテナを作成、実行する

docker container ls (-a)

実行中のコンテナの一覧を表示する -aで終了したコンテナを含む一覧を表示する

docker container stop (コンテナIDまたはコンテナ名)

実行しているコンテナを終了する

docker container restart (コンテナIDまたはコンテナ名)

コンテナの再起動

docker container rm (コンテナIDまたはコンテナ名)

コンテナの破棄

docker container logs

Dockerコンテナの標準出力を表示

docker container exec (コンテナIDまたはコンテナ名) (コンテナ内で実行するコマンド)

実行中のコンテナでのコマンド実行

docker container cp (コンテナIDまたはコンテナ名):(コンテナ内のコピー元) (ホストのコピー先)

ファイルのコピー、コンテナ間でのファイルのやりとりのために使用

ActiveAdminのスタイルが他ページまで影響してしまう。

Active Adminという管理画面作成用のgemを入れるとき、Active AdminのCSSが他のページにまで影響してしまうという問題が起こった。

f:id:stks56:20190311054806p:plain
"ヘッダーのリンクに下線が追加されてしまっていたり、ログインボタンが変わってしまっている

totutotu.hatenablog.com

以上の方法で回避。

何をしているかというと

まずadminというフォルダにActiveAdmin関係のフォルダを用意

マニフェストファイルにて application.css

*= require_tree .
// ディレクトリ内のすべてのファイルを読み込み
↓
*= requiire_directory .
// ディレクトリ直下のファイルのみ読み込み

に書き換え

ActiveAdmin側の設定でadminフォルダ内のファイルを読み込むように設定

ということだそうだ

アセットパイプライン周りの知識が薄いと感じたのでそれはまたあとで復習することにしよう。