JapanContainerDaysに参加しました!
JapanContainerDaysの概要
- KubernetesやDockerの仕組みや導入例などの紹介
参加したセッション
- サイバーエージェントにおけるプライベートコンテナ基盤AKEを支える技術
- マイクロサービスアプリケーションとしての機械学習
- "Yahoo! JAPAN の Kubernetes-as-a-Service" で加速するアプリケーション開発
- Kubernetesのセキュリティのベストプラクティス
- Horizontal Pod Autoscalers
- Container Networking Deep Dive
- Spinnakerを利用したKubernetesへの継続的デリバリ
- Kubernetes 運用設計ガイド
- Dockerだけじゃないコンテナruntime徹底比較!
- Istioと共にマイクロサービスに立ち向かえ!
サイバーエージェントにおけるプライベートコンテナ基盤AKEを支える技術
概要
- AKEはKuernetesがまだ本番で使用できないときに作成されたプライベートコンテナ基盤
- 主な機能
- Docker swarmとKuernetes
- OpenStack integrationとの連携
- LoadBalancer
- アドオン
- Kibana
- Elasticsearch
- Datadog etc...
- サーバーの拡張
- 複数のContainerRuntimeのサポート予定
感想
- vanilla Heatを使用してKubernetesのクラスタを構築しているが、vanilla Heatを何か知らなかった
- 独自のシステムなので管理や更新が大変と言っていた(そうだろうなという感じはすごいしていた)
- OSのイメージを読み込んでスケールするのでKubersprayよりもスケールするのが早いらしい
- Kubernetesでは間に合わないくらいスケールしないといけないのだろうか、それともすでに使用しているサービスがAKEに依存しているのだろうか
マイクロサービスアプリケーションとしての機械学習
概要
- 機械学習のモデルをKubernetes上で動かすまでの話
感想
"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のセキュリティのベストプラクティス
概要
- webアプリケーションをKubernetes上で動かす時を例にしたセキュリティの話
- 必要な箇所に必要な分だけの権限を付与する
- pod内の処理はrootユーザー以外で実行するようにする
- AppArmmorの設定をする
- 可能ならSELinuxを利用する
- seccomp の設定をする
- syscallの一部を許可する
- linuxのnamespaceを突破を防ぐ
感想
- Kubernetesを本番環境に導入しようとしているがセキュリティについてあまり知らなかったのでとても参考になった
- トークンを抜く方法は説明されなかったが抜く方法があるのだろうか
- Dockerを使用しているときnamespace問題を感じていたが解決の方法を知らなかった
- (英語で聞くのかなと思っていた日本語で説明していただけのでとても助かった)
Horizontal Pod Autoscalers
概要
- Multi Metricsを利用してスケールさせる話
- マイクロサービスが 124 ある
- カスタムメトリクスをPrometheusで処理をさせている
感想
- Prometheusという監視ツールを初めて知った。ここ1年くらいででてきたらしい
- スケールさせる方法を具体的に認識していなかったのでとても参考になった
- シナリオ作成の大切を理解した
Container Networking Deep Dive
www.slideshare.net
概要
- コンテナで使用されているネットワークの話
- FlannelとCalicoの比較
- ネットワークのパフォーマス改善について
感想
- ネットワークに無知すぎて聞いてて辛くなった
- 雰囲気で使うのはやめたいと強く思った
Spinnakerを利用したKubernetesへの継続的デリバリ
概要
- Spinnakerの利用方法についての話
- さまざまなデプロイ方法を提供している
- 障害の耐久テストをできる
感想
- 現在はRailsアプリをCapistranoでKubernetesにデプロイをしているので、Spinnakerを使いたくなった
- 障害の耐久テストは実施できる環境がなくチームとして問題になっていたのでぜひ試したい
Kubernetes 運用設計ガイド
概要
- Kubernetesを導入する時の話
- Kubernetesを導入するだけではなく、人が作るので技術と組織は表裏一体。チームを分けて責務を明確にすることが大切
- 速く動く。速さというのは急ぐことからではなく、何かを無くすことから生まれる
- Kubernetes自体の狙いを理解し、それにそった運用をする
- 環境ごとにクラスタを作成すると環境ごとに管理のコストが発生するので、リージョンごとにクラスタは1つにし、namespaceで環境ごとにわける
- 管理・監視が難しくなるので1コンテナ1プロセスにする
感想
- 監視がとても大切(当たり前
- チームのあり方、責務の分担とても参考になった
- まさに本番に導入しようとしていたので、どこまでやって本番に導入するかが見えた(気がする
- GUIのデプロイツールはコードに残らないから良くないらしい
- 技術だけの話ではなくチームの話をしているのは珍しいなと思った
- 自分的にはこのイベントで一番いいなぁと感じる内容だった
Dockerだけじゃないコンテナ runtime 徹底比較!
概要
- 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と共にマイクロサービスに立ち向かえ!
概要
- Kubernetesを利用したマイクロサービスの運用
- 複雑になったアプリケーション間の通信の管理にIstioを利用する
- Istioを利用すると障害耐久テストを行うことができる
感想
- Istio すごい。障害対応のテストをyamlで定義できるのすごい。
- 導入したいが半年くらいインフラだけを担当しないとしっかり導入できないような気がしている
- マイクロサービスの運用が辛いとおっしゃっている方が多かった
Docker Birthday #5 Celebrationに参加しました!
引用: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周年記念のハンズオンに参加しました!
全体の流れと感想を書きます!
アジェンダ
- Dockerの紹介
- ハンズオン
- 交流会
dockerの紹介
最初のスライドです
スピーカーはKunal Kushwahaさんです
英語でDockerについて説明していただきました
とても丁寧に説明していただきました(私は単語しか聞き取れずほぼ理解できませんでしたw)
こうゆう時に英語ができなくて非常に残念だなと毎回感じます
www.slideshare.net
次のスライドです
スピーカーはYutaka Ichikawaさんです
こちらもDockerについての説明です
日本語で説明していただいたので内容を把握できましたw
スライドの内容としては
- 仮想化の方法
- Dockerの簡単な使い方の説明
- Docker composeの簡単な説明
です
dockerは普段開発環境で使っているので使い方については少し知っていましたが、仮想化技術についてはあまり理解せず使っていたので簡単にではありますがどのように仮想化がされているかがわかりました
ハンズオン
ハンズオンはこちらを使用しました
イベント限定でDockerEEを利用することが可能でそれを利用してハンズオンを進めます
Docker EEではGUIでコンテナの操作ができたりといろんな機能がありました(ハンズオンを進めるのでパツパツでよく見れなかった) 普段使う機会がないので少し操作できてよかったなと思います
ハンズオンの種類は6種類ありました
- Docker 101 - Linux
- Dockerのimageの作成とswarmを試すことができます
- こちらはDockerEEが必要ありません
- Docker 101 - Windows Containers
- 名前の通りですがwindowsでのDockerの操作をできますが今はwindownsのパソコンが必要です
- Kubernetes on Docker for Mac and Docker for Windows
- ローカルでk8sとdockerを連携して利用します
- DockerのedgeバージョンをインストールしGUIでk8sを操作したりします
- こちらはDockerEEが必要ありません
- Modernizing Traditional .NET / Windows Apps
- Deploying Multi-OS applications with Docker EE
- Docker EE を利用してlinuxのコンテナとwindowsのコンテナをオーケストレーションします
私は最後の Deploying Multi-OS applications with Docker EE
をやっていたのですが時間内に終わりませんでした(汗
交流会
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に読み込むファイルを設置します
- amazonのcsvではカテゴリーの名前が「/」区切りで親から子まで表示されています
- 例:ビューティー/オーラルケア
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:stop
とbundle exec cap puma:start
を実行すると設定が反映されます。
上記の設定ファイルがサーバーに出力される理由
- capistrano-pumaでは任意のテンプレートの出力に対応しており、指定された場所に存在するファイルをテンプレートとして使用して設定ファイルを出力します。