JapanContainerDaysに参加しました!

JapanContainerDaysの概要

  • KubernetesやDockerの仕組みや導入例などの紹介

参加したセッション

サイバーエージェントにおけるプライベートコンテナ基盤AKEを支える技術

speakerdeck.com

概要

  • AKEはKuernetesがまだ本番で使用できないときに作成されたプライベートコンテナ基盤
  • 主な機能
    • Docker swarmとKuernetes
    • OpenStack integrationとの連携
    • LoadBalancer
    • アドオン
      • Kibana
      • Elasticsearch
      • Datadog etc...
    • サーバーの拡張
    • 複数のContainerRuntimeのサポート予定

感想

  • vanilla Heatを使用してKubernetesのクラスタを構築しているが、vanilla Heatを何か知らなかった
  • 独自のシステムなので管理や更新が大変と言っていた(そうだろうなという感じはすごいしていた)
  • OSのイメージを読み込んでスケールするのでKubersprayよりもスケールするのが早いらしい
  • Kubernetesでは間に合わないくらいスケールしないといけないのだろうか、それともすでに使用しているサービスがAKEに依存しているのだろうか

マイクロサービスアプリケーションとしての機械学習

speakerdeck.com

概要

  • 機械学習のモデルをKubernetes上で動かすまでの話

感想

  • 機械学習について初めて話を聞いて、エンジニアという括りは大きすぎるのでないかと改めて感じた
  • デプロイにはSpinnakerを利用していた。Spinnakerの存在を初めて知る

"Yahoo! JAPAN の Kubernetes-as-a-Service" で加速するアプリケーション開発

www.slideshare.net

概要

  • Yahoo!すばとくのサービスをPHPからJavaにリプレイスにインフラにKubernetes-as-a-Serviceを導入
  • リリースまで数時間かかっていたが10分程度で終了するようになった
  • 開発者の思考をKubernetesにあったものにすることが必要だった
  • Kubernetes-as-a-Serviceは開発着手から1年ほどで一部の本番環境で利用されるようになった
  • Kubernetes-as-a-Serviceの主な機能
    • 開発者が自由にクラスを作成・削除・設定変更可能
    • セルフヒーリング
    • モニタリング
    • スケーラブル
  • 目標は全てを自動化すること

感想

  • 大企業は先進的な技術はあまり導入していないという、なぞの固定概念を持っていたが一気に払拭された
  • CAのようにインフラのツールを作成しており純粋にすごいと思った
  • リリースまで10分で終わるということはデプロイしてからの起動までがだいぶ早そう
  • 全てを自動化するという目標かっこいい、自分もやっていきたい

Kubernetesのセキュリティのベストプラクティス

speakerdeck.com

概要

  • webアプリケーションをKubernetes上で動かす時を例にしたセキュリティの話
  • 必要な箇所に必要な分だけの権限を付与する
  • pod内の処理はrootユーザー以外で実行するようにする
  • AppArmmorの設定をする
    • hostへのアクセスを制限する
    • ubuntu/debian系に入っている
    • SELinuxと同じような機能を提供している
  • 可能ならSELinuxを利用する
  • seccomp の設定をする
    • syscallの一部を許可する
    • linuxのnamespaceを突破を防ぐ

感想

  • Kubernetesを本番環境に導入しようとしているがセキュリティについてあまり知らなかったのでとても参考になった
  • トークンを抜く方法は説明されなかったが抜く方法があるのだろうか
  • Dockerを使用しているときnamespace問題を感じていたが解決の方法を知らなかった
  • (英語で聞くのかなと思っていた日本語で説明していただけのでとても助かった)

Horizontal Pod Autoscalers

speakerdeck.com

概要

  • Multi Metricsを利用してスケールさせる話
  • マイクロサービスが 124 ある
  • カスタムメトリクスをPrometheusで処理をさせている

感想

  • Prometheusという監視ツールを初めて知った。ここ1年くらいででてきたらしい
  • スケールさせる方法を具体的に認識していなかったのでとても参考になった
  • シナリオ作成の大切を理解した

Container Networking Deep Dive

www.slideshare.net

概要

  • コンテナで使用されているネットワークの話
  • FlannelとCalicoの比較
  • ネットワークのパフォーマス改善について

感想

  • ネットワークに無知すぎて聞いてて辛くなった
  • 雰囲気で使うのはやめたいと強く思った

Spinnakerを利用したKubernetesへの継続的デリバリ

speakerdeck.com

概要

  • Spinnakerの利用方法についての話
  • さまざまなデプロイ方法を提供している
  • 障害の耐久テストをできる

感想

  • 現在はRailsアプリをCapistranoでKubernetesにデプロイをしているので、Spinnakerを使いたくなった
  • 障害の耐久テストは実施できる環境がなくチームとして問題になっていたのでぜひ試したい

Kubernetes 運用設計ガイド

speakerdeck.com

概要

  • Kubernetesを導入する時の話
  • Kubernetesを導入するだけではなく、人が作るので技術と組織は表裏一体。チームを分けて責務を明確にすることが大切
  • 速く動く。速さというのは急ぐことからではなく、何かを無くすことから生まれる
  • Kubernetes自体の狙いを理解し、それにそった運用をする
  • 環境ごとにクラスタを作成すると環境ごとに管理のコストが発生するので、リージョンごとにクラスタは1つにし、namespaceで環境ごとにわける
  • 管理・監視が難しくなるので1コンテナ1プロセスにする

感想

  • 監視がとても大切(当たり前
  • チームのあり方、責務の分担とても参考になった
  • まさに本番に導入しようとしていたので、どこまでやって本番に導入するかが見えた(気がする
  • GUIのデプロイツールはコードに残らないから良くないらしい
  • 技術だけの話ではなくチームの話をしているのは珍しいなと思った
  • 自分的にはこのイベントで一番いいなぁと感じる内容だった

Dockerだけじゃないコンテナ runtime 徹底比較!

speakerdeck.com

概要

  • Kubernetesでstableになっているruntimeの比較
  • 主な種類
    • Docker
    • Frakti
    • cri-o
    • containerd
  • OCI とCRI という規格が定められており、Kubernetesとruntimeの連携で利用されている
  • Container runtimeの中にruntimeが存在し、Container runtimeはHigh-level runtime、runtimeはLow-level runtimeと呼ばれている
  • runtime との組み合わせでパフォーマンスが変化する。いまのところDockerが一番良さそう

感想

  • runtimeという言葉を初めて聞いた
  • Kubernetesがどこでコンテナを操作してるかイマイチ理解していなかったが少し理解することができた
  • KubernetesとDockerの組み合わせしかないと思っていたのでとても面白かった
  • OCIとCRIというのも初めて聞いた

Istioと共にマイクロサービスに立ち向かえ!

speakerdeck.com

概要

  • Kubernetesを利用したマイクロサービスの運用
  • 複雑になったアプリケーション間の通信の管理にIstioを利用する
  • Istioを利用すると障害耐久テストを行うことができる

感想

  • Istio すごい。障害対応のテストをyamlで定義できるのすごい。
  • 導入したいが半年くらいインフラだけを担当しないとしっかり導入できないような気がしている
  • マイクロサービスの運用が辛いとおっしゃっている方が多かった

Docker Birthday #5 Celebrationに参加しました!

https://connpass-tokyo.s3.amazonaws.com/thumbs/51/38/513841c07d179531da0b4504f295d8be.png 引用:https://connpass-tokyo.s3.amazonaws.com/thumbs/51/38/513841c07d179531da0b4504f295d8be.png:image=https://connpass-tokyo.s3.amazonaws.com/thumbs/51/38/513841c07d179531da0b4504f295d8be.png

2018/3/23に開催されたDockerの5周年記念のハンズオンに参加しました!

全体の流れと感想を書きます!

アジェンダ

  1. Dockerの紹介
  2. ハンズオン
  3. 交流会

dockerの紹介

speakerdeck.com

最初のスライドです

スピーカーはKunal Kushwahaさんです

英語でDockerについて説明していただきました

とても丁寧に説明していただきました(私は単語しか聞き取れずほぼ理解できませんでしたw)

こうゆう時に英語ができなくて非常に残念だなと毎回感じます

www.slideshare.net

次のスライドです

スピーカーはYutaka Ichikawaさんです

こちらもDockerについての説明です

日本語で説明していただいたので内容を把握できましたw

スライドの内容としては

  • 仮想化の方法
  • Dockerの簡単な使い方の説明
  • Docker composeの簡単な説明

です

dockerは普段開発環境で使っているので使い方については少し知っていましたが、仮想化技術についてはあまり理解せず使っていたので簡単にではありますがどのように仮想化がされているかがわかりました

ハンズオン

ハンズオンはこちらを使用しました

github.com

イベント限定でDockerEEを利用することが可能でそれを利用してハンズオンを進めます

Docker EEではGUIでコンテナの操作ができたりといろんな機能がありました(ハンズオンを進めるのでパツパツでよく見れなかった) 普段使う機会がないので少し操作できてよかったなと思います

ハンズオンの種類は6種類ありました

私は最後の Deploying Multi-OS applications with Docker EE をやっていたのですが時間内に終わりませんでした(汗

交流会

f:id:writing_penguin:20180401213205j:plain

5周年記念のケーキがありました!

Dockerのキャラクターの部分はいただけませんでしたがケーキはいただきました :)

ケーキの他にはピザやビールをいただきました!

新しいことを教えていただいたうえにご馳走になって至れり尽くせりでした

また、他の参加者の方をお話しさせていただいて普段の業務では関わることがない部分についても知れたのでとても有意義な時間でした

最後に

Docker5周年おめでとうございます!

今回の運営の方々大変お世話になりました!

Product Advertising API(PAAPI)を使って50万商品を取得する

バージョン

前提

  • 今回は化粧品関連の商品を取得したかったので、アマゾンの「ビューティー」カテゴリーの下のカテゴリー全ての商品を取得します
  • amazonのbrowse node idを使用します
  • amazonのカテゴリーはamazonのサイトからダウンロードできたのですが場所がわからなくってしまったので見つけ次第貼りますm( )m
  • 商品とカテゴリーを多対多にします(一番下の子カテゴリーから一番上の親カテゴリーを紐づけるため)

準備

itemモデルとmigration

  • amazonでは商品ごとにユニークな asin があるので、それをこちらでも取得しユニーク制約をつけることで商品の重複を防ぎます
class CreateItems < ActiveRecord::Migration[5.1]
  def change
    create_table :items do |t|
      t.string :name, null: false
      t.references :brand, index: true
      t.string :asin
      t.string :image_url
    end

    add_index :items, :asin, unique: true
  end
end
class Item < ApplicationRecord
  has_many :item_category_relations
  has_many :categories, through: :item_category_relations
  validates :name, length: {maximum: 255}, presence: true
  validates :asin, uniqueness: true, presence: true
end

categoryモデルとmigration

  • ancestryカラムはgemの ancestry で使用します
class CreateCategories < ActiveRecord::Migration[5.1]
  def change
    create_table :categories do |t|
      t.string :name, null: false
      t.string :browse_node_id
      t.string :ancestry, index: true
      t.integer :acquire_status, default: 0, null: false
    end
  end
end
class Category < ApplicationRecord
  has_ancestry
  has_many :item_category_relations
  has_many :items, through: :item_category_relations
  validates :name, length: {maximum: 255}, presence: true
  enum acquire_status: {unacquired: 0, acquired: 1}
end

item_category_relationモデルとmigration

class CreateItemCategoryRelations < ActiveRecord::Migration[5.1]
  def change
    create_table :item_category_relations do |t|
      t.references :item, index: true, null: false
      t.references :category, index: true, null: false
    end
  end
end
class ItemCategoryRelation < ApplicationRecord
  belongs_to :item
  belongs_to :category
end

categoryの一括登録seed

  • db/fixtures/categories.csvに読み込むファイルを設置します
  • amazoncsvではカテゴリーの名前が「/」区切りで親から子まで表示されています
    • 例:ビューティー/オーラルケ
CSV.foreach(Rails.root.join('db', 'fixtures', "categories.csv")) do |row|
  ActiveRecord::Base.transaction do
    browse_node_id = row.first
    names = row.last.split('/')
    category = Category.create! name: names.last, browse_node_id: browse_node_id
    if names.count > 1 #  親カテゴリーが存在する時
      category.parent = Category.find_by name: names.last(2).first
      category.save!
    end
  end
end

実際に使ったコード

  • 非常に汚いコードです(恥ずかしい
  • 価格とカテゴリーで検索をしています
  • ひたすら再帰して商品を取得させます
  • 商品数が多いとリクエスト回数が増えて再帰する回数も増えて stack level too deep になってしまう(3カテゴリーほど全ての商品を取得できなかった)
  • 実装の際に意識したこと
    • 1つの検索クエリで10ページ(10件/ページ)までしか取得できないので検索結果が100件以下になるように検索をする
    • 検索結果が多い時にその時の価格を半分にして再度検索をする
    • 逆に検索結果が少ない時(10件以下の時)は価格を倍にして検索をする
    • サーバーでひたすら走らせるので適宜 puts を入れてログを残す
    • 取得に途中で失敗しタスクが中断されると最初からまた取得し直すのは辛いので acquire_status をカテゴリーに持たせ、どこまで習得したかを管理してました
  • @last_item_count != item_count の条件分岐をしているについて
    • 価格差1円で検索しても100件以上あるケースがあり、それは頑張っても取得できなさそうなので前回検索した時の商品数と今回検索した商品数が同じ時は商品の取得を開始するようになっています
namespace :amazon_api do
  task fetch_items: :environment do
    def amazon_api(browse_node_id, max, min, item_page)
      Amazon::Ecs.item_search(
        '',
        country: 'jp', # 自分が登録しているamazonの国
        browse_node: browse_node_id,
        minimum_price: min,
        maximum_price: max,
        search_index: 'Beauty',
        response_group: 'Images,ItemAttributes',
        item_page: item_page
      )
    end

    def blank_upside?(browse_node_id, max, min)
      result = amazon_api(browse_node_id, max, min, 1).doc.at('TotalResults').text == '0'
      return result
    rescue Amazon::RequestError
      sleep(10)
      blank_upside?(browse_node_id, max, min)
    end

    def create_item(row, c)
      name = row.at('Title')&.text
      return if name.blank?
      brand = row.at('Manufacturer').blank? ? nil : Brand.find_or_create_by(name: row.at('Manufacturer').text)
      asin  = row.at('ASIN').text
      image_url = (row.at('LargeImage') || row.at('MediumImage') || row.at('SmallImage'))&.at('URL')&.text || nil
      Item.create name: name, asin: asin, image_url: image_url, brand: brand, categories: c.path
    end

    def fetch(c, price, diff, page)
      res = amazon_api(c.browse_node_id, (price + diff), price, page)
      item_count = res.doc.at('TotalResults').text.to_i
      # 最後に取得した時の件数と異なる時かつ
      # 取得した商品の数が1〜10かつ
      # pageが1ページ目のとき
      if @last_item_count != item_count && item_count.in?(1..10) && page == 1
        @last_item_count = item_count
        fetch(c, price, ((diff.zero? ? 10 : diff)*2), 1)
        return
      end
      if diff != 1 && res.doc.at('TotalResults').text.to_i > 100 && page == 1
        fetch(c, price, ((diff.zero? ? 10 : diff)/2), 1)
        return
      end
      if res.doc.at('TotalResults').text == '0'
        # 現在の指定された価格で商品が見つからない時は上限の上を検索して見つからなければ次のカテゴリーに進める
        return if blank_upside?(c.browse_node_id, (price + 10000), price+diff)
        below(c, price, ((diff.zero? ? 10 : diff)*2), page, res)
        return
      end
      # 正常パターン
      res.doc.at('Items').search('Item').each {|row| create_item row, c}
      below(c, price, (diff.zero? ? 10 : diff), page, res)
    rescue Amazon::RequestError
      sleep(10)
      fetch(c, price, (diff.zero? ? 10 : diff), page)
    end

    def below(c, price, diff, page, res)
      if page == 10
        fetch(c, (price + diff), diff, 1)
        return
      end

      if res.doc.at('TotalPages').text.to_i > page
        fetch(c, price, diff, page + 1)
        return
      end

      fetch(c, (price + diff), diff, 1)
    end

    most_child_categories = Category.unacquired.where.select{|c| c.children.blank?}
    most_child_categories.each do |category|
      fetch(category, 0, 100, 1)
      category.acquired!
    end
  end
end

capistrano-pumaを使用したpuma_worker_killerの設定方法

バージョン

  • rails 5.1.4
  • capistrano3-puma 3.1.1
  • puma_worker_killer 0.1.0

動けばOK!という方はこちらで

  • lib/capistrano/templates/puma-production.rb を作成し、以下の内容を記述してください。
#!/usr/bin/env puma

directory '<%= current_path %>'
rackup "<%=fetch(:puma_rackup)%>"
environment '<%= fetch(:puma_env) %>'
<% if fetch(:puma_tag) %>
tag '<%= fetch(:puma_tag)%>'
<% end %>
pidfile "<%=fetch(:puma_pid)%>"
state_path "<%=fetch(:puma_state)%>"
stdout_redirect '<%=fetch(:puma_access_log)%>', '<%=fetch(:puma_error_log)%>', true


threads <%=fetch(:puma_threads).join(',')%>

<%= puma_plugins %>

<%= puma_bind %>
<% if fetch(:puma_control_app) %>
activate_control_app "<%= fetch(:puma_default_control_app) %>"
<% end %>
workers <%= puma_workers %>
<% if fetch(:puma_worker_timeout) %>
worker_timeout <%= fetch(:puma_worker_timeout).to_i %>
<% end %>

<% if puma_daemonize? %>
daemonize
<% end %>

restart_command '<%= fetch(:puma_restart_command) %>'

<% if puma_preload_app? %>
preload_app!
<% else %>
prune_bundler
<% end %>

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = "<%= fetch(:bundle_gemfile, "#{current_path}/Gemfile") %>"
end

<% if puma_preload_app? and fetch(:puma_init_active_record) %>
before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end
<% end %>

before_fork do
  require 'puma_worker_killer'
  # ここの設定は適宜変更してください
  PumaWorkerKiller.config do |config|
    config.ram           = 1024 # mb
    config.frequency     = 5    # seconds
    config.percent_usage = 0.98
    config.rolling_restart_frequency = 6 * 3600 # 12 hours in seconds
    config.reaper_status_logs = true # setting this to false will not log lines like:
    # PumaWorkerKiller: Consuming 54.34765625 mb with master and 2 workers.

    config.pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed" }
  end
  PumaWorkerKiller.start
end
  • bundle exec cap puma:config を実行して設定ファイルをサーバーに設置します。
  • bundle exec cap puma:stopbundle exec cap puma:start を実行すると設定が反映されます。

上記の設定ファイルがサーバーに出力される理由