TECH PLAY

スマートキャンプ株式会社

スマートキャンプ株式会社 の技術ブログ

226

スマートキャンプでインフラエンジニアをしている入山です。 みなさんはKubernetes(k8s)を知っていますか? Kubernetesは知らなくても、Dockerを知っている、またはローカルでの開発でDockerを使っているエンジニアの方は多いのではないでしょうか? スマートキャンプではこれまで、ローカル環境では仮想コンテナ(docker)で開発を行い、テスト環境や本番環境はサーバー上へデプロイする方式でしたが、新規プロダクトではKubernetesを導入し、テスト環境や本番環境についてもアプリケーションを仮想コンテナでデプロイする方式に変更しました。 Kubernetesといえば マネージドサービス (Kubernetesを運用管理するアウトソーシングサービス)が主流であり、最近でも EKS東京リージョンのリリース が発表されましたが、新規プロダクトでは ノンマネージド でKubernetesを構築しました。(EKS東京リージョンを待ちきれませんでした。。。) 今回は、 ノンマネージドでのKubernetesクラスタの構築 について、紹介したいと思います! Kubernetesとは Kubernetesは、コンテナ化されたアプリケーションの デプロイ、スケーリング、管理を自動化 するためのコンテナオーケストレーションツールです。 コンテナオーケストレーションツールを利用することにより、 複数のホスト上で複数の仮想コンテナを一元的に管理 することが簡単に実現できます。 これにより、仮想コンテナを利用した一定規模以上のプロダクション利用に耐えうるシステムが構築できるようになりました。 Kubernetesは、 3大クラウドプロバイダ(google、Amazon、Microsoft)のマネージドサービス を筆頭に、コンテナオーケストレーションツールの デファクトスタンダード として確固たる地位を獲得しています。 Production-Grade Container Orchestration - Kubernetes Kubernetes環境の選択肢 Kubernetesは、様々なプラットフォーム上にクラスタを構築することが可能で、大きく分けると以下 3種類の構成/構築方法 があります。 ローカルKubernetes(Minikube,Dockerなど) 1台のローカルマシン上にオールインワン構成で手軽にKubernetesを構築(一部利用不可機能有り) Kubernetes構築ツール(Kubeadm,Rancherなど) オンプレミスやクラウドサーバ上で複数ノードのKubernetesクラスタ構築に必要なコンポーネントのインストールや設定などを自動化 マネージドKubernetesサービス(EKS,GKE,AKSなど) クラウドサービス上でのKubernetesクラスタやノード管理、ロードバランサ連携などを簡単に利用可能 スマートキャンプでは従来よりインフラにAWSを使用しているのですが、新規プロダクトの環境構築時はEKSが東京リージョン未対応であったため、 構築ツール(kubeadm) を利用してKubernetesクラスタを構築しました。 構築環境 今回は例として、以下のKubernetesクラスタを Kubeadm を使用して構築していきます。 コンテナランタイムは、デフォルトの Docker を使用します。 環境構成 AWS/EC2(ノンマネージド) Master : 1台 Worker : 2台 構築方法 Kubeadm バージョン OS : Ubuntu 16.04.5 LTS Docker : 18.06.1-ce kubeadm : v1.12.2 kubelet : v1.12.2 kubectl : v1.12.2 flannel : v0.10.0 Kubernetesクラスタ構築手順 Kubernetesクラスタは、 Masterノード と Workerノード で構成されており、各ノードによって構築手順が多少異なります。 1. パッケージのインストール(対象:Master,Worker) 全ノードで Docker と Kubernetes のパッケージをインストールします。今回は、各パッケージのバージョンを指定してます。 Docker sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common sudo su - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - add-apt-repository " deb https://download.docker.com/linux/ $( . /etc/os-release ; echo " $ID " ) $( lsb_release -cs ) stable " exit sudo apt-get update && sudo apt-get install -y docker-ce = 18 . 06 . 1 ~ce~3-0~ubuntu Kubernetes sudo apt-get update && sudo apt-get install -y apt-transport-https curl sudo su - curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - cat <<EOF >/etc/apt/sources.list.d/kubernetes.list deb https://apt.kubernetes.io/ kubernetes-xenial main EOF exit sudo apt-get update && sudo apt-get install -y kubelet = 1 . 12 .2-00 kubeadm = 1 . 12 .2-00 kubectl = 1 . 12 .2-00 2. Masterノードのセットアップ(対象:Master) Kubeadmの初期化(Masterの構築) Masterとなる1台のノードで以下のコマンドを実行し、クラスタを構築します。 --pod-network-cidr オプションは、Podネットワーク(クラスタ内ネットワーク)で使用するアドオンの種別を選択します。今回は、 Flannel を使用する為、 --pod-network-cidr=10.244.0.0/16 を指定しています。 --apiserver-advertise-address オプションは、使用するIPアドレスを指定できます。今回はプライベートIPを指定します。指定しなければデフォルトゲートウェイのインターフェースが選択されます。 sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address 172 . 63 . 4 . 131 【コマンド実行結果例】 ubuntu@master:~$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address 172 . 63 . 4 . 131 I0129 06:18:37. 035164 24995 version.go:236 ] remote version is much newer: v1. 13 . 2 ; falling back to: stable-1. 12 [ init ] using Kubernetes version: v1. 12 . 5 [ preflight ] running pre-flight checks         :         : [ addons ] Applied essential addon: CoreDNS [ addons ] Applied essential addon: kube-proxy Your Kubernetes master has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME /.kube sudo cp -i /etc/kubernetes/admin.conf $HOME /.kube/config sudo chown $( id -u ) : $( id -g ) $HOME /.kube/config You should now deploy a pod network to the cluster. Run " kubectl apply -f [podnetwork].yaml " with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ You can now join any number of machines by running the following on each node as root: kubeadm join 172 . 63 . 4 .131:6443 --token 8vij1a.g5cl58u5k4suh1v6 --discovery-token-ca-cert-hash sha256:7216e0860cfb9c8ad5d9e87afebe155b27c5107d80f68515af4eac34b8b33ce7 認証ファイルの配置 kubectl は鍵による接続設定(鍵の配置)が必要となります。 kubeadm init 実行時に出力されている以下のコマンドを実行します。 mkdir -p $HOME /.kube sudo cp -i /etc/kubernetes/admin.conf $HOME /.kube/config sudo chown $( id -u ) : $( id -g ) $HOME /.kube/config クラスタのノード状態を表示すると、以下のような結果が出力されます。 【ノード状態確認結果】 ubuntu@master:~$ kubectl get nodes NAME STATUS ROLES AGE VERSION master NotReady master 12m v1. 12 . 2 3. Workerノードのセットアップ(対象:Worker) Kubernetesクラスタへの追加 WorkerをKubernetesクラスタに追加するために、Worker側で Kubeadm join コマンドを実行する必要があります。こちらのコマンドについても、 Kubeadm init の実行結果に出力されています。 ※環境毎に異なる為、出力されたコマンドを実行することに注意してください。 sudo kubeadm join 172 . 63 . 4 .131:6443 --token 8vij1a.g5cl58u5k4suh1v6 --discovery-token-ca-cert-hash sha256:7216e0860cfb9c8ad5d9e87afebe155b27c5107d80f68515af4eac34b8b33ce7 【実行結果例】 ubuntu@worker1:~$ sudo kubeadm join 172 . 63 . 4 .131:6443 --token 8vij1a.g5cl58u5k4suh1v6 --discovery-tok en-ca-cert-hash sha256:7216e0860cfb9c8ad5d9e87afebe155b27c5107d80f68515af4eac34b8b33ce7 [ preflight ] running pre-flight checks         :         : [ patchnode ] Uploading the CRI Socket information " /var/run/dockershim.sock " to the Node API object " worker1 " as an annotation This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run ' kubectl get nodes ' on the master to see this node join the cluster. Master構築時に出力されたjoinコマンドは、以下のコマンドを実行することで再度出力することができます。 kubeadm token create --print-join-command この時点でクラスタのノード状態を表示すると、以下のようにWorkerノードが追加されています。 【ノード状態確認結果】 ubuntu@master:~$ kubectl get nodes NAME STATUS ROLES AGE VERSION master NotReady master 26m v1. 12 . 2 worker1 NotReady < none > 5m14s v1. 12 . 2 worker2 NotReady < none > 3s v1. 12 . 2 4. Podネットワークのデプロイ(対象:Master) 上記のノード状態確認結果では、各ノードのSTATUSが NotReady になっていますが、Kubernetesクラスタでは異なるノードのPod間で通信するための専用ネットワークを構築する必要があります。 Podネットワークも複数の選択肢がありますが、今回は Flannel を選択しています。以下のコマンドでデプロイします。 kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/bc79dd1505b0c8681ece4de4c0d86c5cd2643275/Documentation/kube-flannel.yml Flannel のデプロイが完了すると、各ノードの状態が Ready になり、運用可能な状態となります。最終的なノード状態は、以下の通りです。 ubuntu@master:~$ kubectl get nodes NAME STATUS ROLES AGE VERSION master Ready master 30m v1. 12 . 2 worker1 Ready < none > 9m52s v1. 12 . 2 worker2 Ready < none > 4m41s v1. 12 . 2 おまけ. 構築に失敗した場合 何かしらの理由でクラスタ構築に失敗した場合やクラスタ構築をやり直したい場合は、以下のコマンドを実行することで、 kubeadm init や kubeadm join を実行する前の状態に戻すことが出来ます。 sudo kubeadm reset 以上で、Kubernetesクラスタの構築は完了です! Installing kubeadm - Kubernetes 今後について 今回は、Kubernetes構築ツールを使用して ノンマネーシド で構築しましたが、冒頭でも触れた通り2018/12/20に EKSが東京リージョンに対応 しました。 Kubernetesを マネージド で構築することで、ロードバランサー連携やスケーリング、アップデートなど、クラスタの管理・運用が 簡単且つ安全 に行えるようになります。 今後、EKSでのKubernetes管理に移行していきたいと思っています!
アバター
この1月に普通自動二輪の免許を取得したので、早く暖かくなってツーリングに行きたいなとそわそわしている笹原です。 みなさんはAWSを使った開発をするときに、どうやってIAMユーザを管理してますか? 開発者に権限を与えすぎれば セキュリティリスクは高まる ので、与える権限を少なくしたいですが、少なくしすぎても 開発・運用時に作業や権限の申請が頻繁に必要 になります。 また、入社時や退職時にIAMユーザの作成・削除をしたり、定期的な棚卸しを実施したりといった運用も管理が煩雑になっていると意外と工数がとられたりするものです。 今回はAWSユーザ管理を管理者からの一方通行ではなく、TerraformとCircle CIを使って開発者からのPull Requestベースでやってみた事例を公開したいと思います! AWSのマルチアカウント構成 Terraform 下準備 ディレクトリ構成 Serviceアカウント CircleCI Job概要 設定 運用フロー 運用上の問題点: IAMユーザの削除時に意図しない変更が行われる 今後について AWSのマルチアカウント構成 AWS Organizationsが入ってからこのような構成が一般的になってきてると思います。 AWS Organizationsによるマルチアカウント戦略とその実装 - クラウドワークス エンジニアブログ AWS Multi-Account Architecture with Terraform, Yeoman, and Jenkins AWSアカウントはLogin用のアカウントを一つ作成して、実際に利用するServiceアカウントにはAssumeRoleを行うことで入るようにしています。 Login 開発者用のIAMユーザを作成する Service サービス毎、本番・開発等の環境毎にAWSアカウントを作成 開発者自身が利用するIAMユーザは基本的に作成せずに、loginアカウントからAssumeRoleを行う Terraform AWSのマルチアカウント構成を運用する上で、AWSコンソール上から手作業でIAMユーザを管理するのは、管理者の負担になるだけでなく属人化も招くので、Terraformで管理します。 下準備 各AWSアカウントの管理をするにあたって、それぞれのアカウントでTerraformを実行することになるので、各アカウントにAssumeRoleできるIAMユーザをLoginアカウント上に作成します。 まず、各AWSアカウント上で、LoginアカウントからAssumeRoleするためのIAMロールを作成します。 このとき、いずれCI上でTerraformを実行することを見据えて、オプションのMFAはチェックを外したままにしておきます。 次に、Loginアカウント上でIAMユーザを作成します。 このIAMユーザには、IAMFullAccessとS3FullAccessに加えて、カスタムポリシーで各アカウントへのAssumeRoleを許可しておきましょう。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": "", " Effect ": " Allow ", " Action ": " sts:AssumeRole ", " Resource ": " arn:aws:iam::*:role/Terraform " } ] } ディレクトリ構成 Terraformのディレクトリ構成はこんな感じです。 Terraformコマンドの実行はルートディレクトリから行います。 この構成のポイントは 外部変数と内部変数と出力については別途専用のファイル( variables.tf と local.tf と outputs.tf )を用意 IAMロールやIAMグループの作成だったり、各アカウントに対する処理は大部分が共通化できるのでmodule化 ├── README.md ├── locals.tf ├── outputs.tf ├── main.tf ├── login-account.tf ├── service-1-account.tf ├── service-2-account.tf ├── ... ├── modules/ │ ├── account/ │ │ ├── variables.tf │ │ ├── outputs.tf │ │ ├── main.tf │ ├── iam/ │ │ ├── group/ │ │ │ ├── variables.tf │ │ │ ├── outputs.tf │ │ │ ├── main.tf │ │ ├── role/ Serviceアカウント 各Serviceアカウントで行うことは以下の2つです。 LoginアカウントからAssumeRoleすることができるIAMロールを作成する IAMロールに対して、各アカウント内で必要な権限を付与する 各ServiceアカウントのTerraform実行は、modules/accountを呼び出して実行します。 以下のように、呼び出し元から対象のServiceアカウントのAWSアカウントIDを渡してやり、下準備で作成したロールにAssumeRoleします。 service-1-account.tf locals { aws_region = " ap-northeast-1 " aws_accounts = { service_1_account = " 000000000001 " service_2_account = " 000000000002 " } } module " service_1_account " { source = " ./modules/account " aws_region = " ${local.aws_region} " aws_account_id = " ${local.aws_accounts[ "service_1_account" ]} " } modules/account/main.tf provider " aws " { region = " ${var.aws_region} " assume_role { role_arn = " arn:aws:iam::${var.aws_account_id}:role/Terraform " } } CircleCI さて、Terraformを使うことで、AWSのIAMユーザ管理の属人化は薄れましたが、Terraformをどのように実行するかには依然として問題はあります。 開発者もTerraformプロジェクトを書き換えて、ローカルで実行できてしまうのでは元も子もありません。 そこで、Terraformの実行をローカルではなくCI上で行うことにします。 弊社ではCircleCI上でTerraformを実行しています。 Job概要 Jobの流れは図のとおりです。 masterブランチでのみ、 terraform apply が走るようになっています。 これでAWSアカウント権限に関する承認・付与を、Pull Requestベースで実行することが可能になりました。 補足が必要そうな点として、Planの前にLintが入っています。これは、 terraform fmt を実行したときに変更されるファイルがあるかどうかを見ています。 設定 使用している設定ファイルも晒します。 工夫したのは versionが2.1から利用できる機能を利用している orbsを使った簡単なSlack通知 executorsによる共通化 terraform planを実行した結果をpersist_to_workspaceを使ってterraform apply時に渡すことでplanで確認したとおりに実行している といったところです。 version : 2.1 orbs : slack : circleci/slack@volatile executors : my-executor : working_directory : ~/work docker : - image : hashicorp/terraform:0.11.10 jobs : checkout_code : executor : my-executor steps : - restore_cache : keys : - source-v1-{{ .Revision }} - source-v1- - checkout - save_cache : key : source-v1-{{ .Revision }} paths : - .git - run : terraform init -input= false - persist_to_workspace : root : ~/ paths : work lint : executor : my-executor steps : - attach_workspace : at : ~/ - run : name : terraform fmt command : | if [ $(terraform fmt | grep -v .terraform | tee fmt_result.txt | wc -l) -gt 0 ] ; then echo "Format of this terraform files is not appropriate:" echo cat fmt_result.txt echo echo "Please run terraform fmt" exit 1 fi plan : executor : my-executor steps : - attach_workspace : at : ~/ - run : terraform plan -input= false -out=terraform.plan - persist_to_workspace : root : ~/ paths : work send-approval-link : docker : - image : buildpack-deps:trusty steps : - slack/notify : message : | Please check and approve Job to deploy. https://circleci.com/workflow-run/${CIRCLE_WORKFLOW_ID} apply : executor : my-executor steps : - attach_workspace : at : ~/ - run : terraform apply -input= false --auto-approve terraform.plan workflows : version : 2 plan_and_apply : jobs : - checkout_code - lint : requires : - checkout_code - plan : requires : - lint - send-approval-link : requires : - plan filters : branches : only : master - waiting-for-approval : type : approval requires : - plan filters : branches : only : - master - apply : requires : - hold 運用フロー 実際の運用フローとしてはこういった形になります。 ただ、Terraform 0.11の仕様ではこのフローでカバーできない範囲があり、そこでは手作業が発生しています。 運用上の問題点: IAMユーザの削除時に意図しない変更が行われる IAMユーザはTerraform上に以下のように設定を記載しています。 locals { user_1 = " user_1 " user_2 = " user_2 " user_3 = " user_3 " aws_iam_users = [ " ${local.user_1} ", " ${local.user_2} ", " ${local.user_3} ", ] } resource " aws_iam_user " " iam_users " { count = " ${length(local.aws_iam_users)} " name = " ${element(local.aws_iam_users, count.index)} " path = " / " force_destroy = true } ここからuser_2をそのままコードからのみ削除を行って、terraform planを行うと ~ aws_iam_user.iam_users [ 1 ] name: " user_2 " => " user_3 " - aws_iam_user.iam_users [ 2 ] として、意図した変更と異なる差分が出力されます。 これは、tfstateファイル内のリソースアドレスに、リストの場合はindexが使用されていることが原因です。 Terraform 0.12ではfor_eachが追加されており、リソースアドレスにindexを使わない方法が提供されるので、解決予定ではあります。 HashiCorp Terraform 0.12 Preview: For and For-Each Terraform 0.12は2019 1Qでリリース予定ではありますが、それまでは手動でリソースアドレスの書き換えを行う必要があります。 terraform state mvを使って、削除したいリソースをリストの最後に持っていくことで削除のみが行われるようになります。 $ terraform state mv aws_iam_user.iam_users [ 1 ] aws_iam_user.iam_users [ 3 ] $ terraform state mv aws_iam_user.iam_users [ 2 ] aws_iam_user.iam_users [ 1 ] $ terraform state mv aws_iam_user.iam_users [ 3 ] aws_iam_user.iam_users [ 2 ] 今後について 今回はAWSのIAMユーザ管理をTerraformとCircleCIを使って行いましたが、利用している開発者向けサービスはAWSだけではありません。 TerraformはAWSやGCPのようなPaaSのみならず、GitHubやNew RelicのようなSaaSまでカバーしているので、そういったところもコード化していく予定です。
アバター
カンバン機能「Projects」って? GitHubのカンバン機能「Projects」をご存知でしょうか? カンバンとは、タスクをカードとし、進捗や状態を表すカラムを移動させることによって視覚化し、管理するモデルとなっています。 カンバン方式を採用したタスク管理ツールとしては、TrelloやAsanaなどがありますが、GitHub上でもリポジトリのProjectsタブを押すことで利用することができます。 困ったこと 😩 Projects内でカードを作成すると、Issueとは別物になるんですよね... そのカードをIssueにするには、「Convert to Issue」をしてTitleを入力してDescriptionを入力する必要があって少し手間だなぁと思っていました。 解決 😆 実は!カードを作成する際に複数行を記述して、「Convert to Issue」をすると、1行目がTitle、それ以降がDescriptionに自動で入るようになっています! これが こうなります! まとめ ちょっとだけ便利...?かもしれないTipでした 😅
アバター
こんにちは。好きなテストフレームワークはやっぱりRSpec、スマートキャンプの今川( @ug23_ )です。 みなさんは負荷テスト、定期的にやっていますか? リリースごと、マイルストーンごと、など単位はさまざまでしょうが、定期的にやる仕組みは重要だと感じています。 今回は社内で負荷テストを定期的に行う仕組みを整えたときについてまとめました。 この記事は 前編 です。前半の5つのポイントについて紹介していきます。 負荷テストの背景 スマートキャンプではいくつかのWebサービスを運営しております。 ほとんどのサービスは公開しているサービスなので、ユーザの挙動によって負荷が大きく変わります。 例えば、テレビに取り上げられるなどしてバズったりすることもあるでしょう。 スケールアップするにしても、スケールアウトするにしても いまの構成でどんだけ耐えられるの?がわからない と対策が的外れなことになりかねません。 公開サービスでユーザが訪問してくる(クローズドな社内サービスではない)場合の負荷テストについて述べていきます。 【ポイント1】正しい負荷テストについて学ぼう 私は正しい負荷テストを体系的に学んだことがなかったので以下の書籍で勉強しました。 対象サービスがAWSで構築されていたことからこちらの本を選びましたが、ネットワーク構成や注意すべき考え方について載っているのでおすすめです。 Amazon Web Services負荷試験入門―クラウドの性能の引き出し方がわかる (Software Design plusシリーズ) 作者: 仲川 樽八 , 森下 健 発売日: 2017/09/23 メディア: 大型本 また、過去行われた負荷テストはどのように行ったか、何が課題で何がうまく行ったのか、その時に実施した社員に聞いてみました。 【ポイント2】負荷テストの終了条件を決めよう あてどなく負荷テストをかけて「毎分○リクエストで死ぬわ!!」とやっても仕方がないのでゴールを決めました。 ソフトウェアテストには終了条件を定めないと、過剰なテストによるムダやテスト不足によるムラが起きてしまいます。 今回とりあげる負荷テストでは以下の3つを満たしたら終了とすることにしました。 ボトルネックとなる要因を特定できていること スケールアップ時またはスケールアウト時の特性がわかっていること 目標負荷を達成するための改修または調査の見通しが立っていること 数値を測定して終わり、でもいいんですが結局それを直すのにどれぐらいの期間と影響範囲があって、どれぐらいの効果が見込めるのかを調査しておかないとムリな開発をする羽目になります。 「これをやったら許容できる負荷を2倍にできるんでやらせてください!」と進言するのにも使えます。 終了条件を決めることはムリ・ムダ・ムラをなくすことにつながると思います。 【ポイント3】対象とするメトリクスとその目標数値を決める 試験の終了条件は決めました。次は実際にどこを目標とするか、です。 エンジニアだけで決めても、事業影響が出たら意味がありません。そのシステムで事業を動かしているメンバーと話し合いました。 今回の試験では以下のように決めています。 メトリクス 事業メンバーはGoogle Analytics上のリアルタイム集計のアクティブユーザ数で現状の来訪者数をウォッチしている アクティブユーザ数とアプリケーションサーバ上で計測される毎分リクエスト数はおおよそ似た遷移かつだいたい近似できそう ⇒ 目標のメトリクスはアプリケーションサーバ上の毎分リクエスト数とする 目標数値 過去にサービスダウンしたことがあったのでその時の負荷には十分耐えたい その時の倍は耐えるように 今後のサービスの伸び幅も考えると1.5倍ぐらいは多めに見込んでおきたい そのさらに1.5倍でも耐えるように ⇒ 目標数値は以前落ちた際の負荷の約3倍とする 【ポイント4】想定する負荷や環境や社内のスキルに合わせて負荷テスト環境を作ろう 負荷テストツール 以前の負荷テストの記録ではjmeterを使っていましたが、Gatlingを採用しました。理由は以下です。 私がScalaに慣れていた 社内にScalaを使える人間がもうひとりいた 自動生成されるレポートがきれいな 自分が一番使いたかった gatling.io Gatling、かっこいいですね! 実際には以下のリポジトリを参考に用意しました。 github.com 公式のリポジトリでも問題はないのですが、開発時のやりやすさを考えSBTで実行できる形でのプロジェクト作成を選びました。 もちろんScalaなのでIntelliJIDEAやENSIMEなどを用いれば型推論をフル活用して開発することができます。 公式のチュートリアルは丁寧にかかれているのでそれに沿ってシナリオをかけば簡単に負荷テストをセットアップできますし、 公式が「Javaがあればこれで動くぜ!」というプロジェクトを作ったzipを配布しているのでそれでも十分だと思います。 非Scalaエンジニアにも(関数型な書き方を多用しなければ)DSLで読みやすいでしょう。 環境作成 動かすサーバは以下のように準備しました。 AWSのCentOS7イメージでEC2を作成 SDKMAN を利用し、 jdk scala sbt をインストール インスタンス上に自作のシナリオを配置、 sbt test testOnly XxxxSpec のように実行して爆撃開始 httpd をインストール、ルートディレクトリとGatlingのレポートをrsyncで同期させてブラウザから閲覧できるようにした 【ポイント5】攻撃に合わせてシナリオを組み立てよう 作ったシナリオ 今回は下記の4種類のシナリオを用意しました。 ヘルスチェックに使っているエンドポイントへのストレステスト ヘルスチェックに使っているエンドポイントへのスパイクテスト 1記事を指定してのストレステスト 1記事を指定してのスパイクテスト ストレステスト: 秒間20リクエストから10分で250リクエストまで増やして限界値をさぐる スパイクテスト: インターバルをあけつつ同時に大量のリクエストを送って限界値をさぐる ※一般的な定義についてはこちらを参照ください。 sites.google.com ヘルスチェックに使っているエンドポイントに対して負荷をかけているのはDBやRedisに依存しないWebアプリケーションとしての限界値を探るために測っています。 また、このタイミングでインスタンスタイプを変えたり、台数を変えたりしてスケールアップ・スケールアウト時の特性を測っています。 実施しなかったシナリオ 他にも負荷テストの手法はありますが、下記の理由から実施しませんでした。 ロングランテスト: 週に1回は必ずリリースしているので長時間稼働は重要視していない ピークロードテスト: 対象となるシステムではピーク値が続くようなケースはなかった 実際のシナリオ Gatlingでは、プロキシを利用してアクセスしたページへのリクエストのシナリオを生成するが用意されていますし、 チートシートが充実しているため、単純なものから複雑なものまでシナリオを作ることができます。 gatling.io 実際につかったのを少し変えたものが以下のシナリオになります。リクエスト数やエンドポイントは例です。 package smartcamp import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.concurrent.duration._ class ApplicationStress extends Simulation { // base URLやヘッダーを定義 val httpProtocol = http .baseUrl( "http://target.example.com" ) .disableCaching .acceptHeader( "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" ) .acceptEncodingHeader( "gzip, deflate, br" ) .acceptLanguageHeader( "ja,en-US;q=0.9,en;q=0.8" ) .userAgentHeader( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0" ) .upgradeInsecureRequestsHeader( "1" ) // ここにシナリオ名と攻撃先を書く val scn = scenario( "一覧APIアクセス" ) .exec(http( "全件取得" ) .get( "/hoge/XXXXXX" )) // シナリオの内容を書く setUp( scn.inject( // 1000リクエスト/秒から30000リクエスト/秒を20分間で遷移させる rampUsersPerSec( 1000 ) to 30000 during ( 20 minutes) ).protocols(httpProtocol) ) } scn.inject(...) の中に、リクエストの内容を書いていくことでシナリオを変えることができます。 また、参考までにスパイクテストは以下のようになっています。injectには Iterable[InjectionProfileFactory] 型を渡せるので以下のようにわたすことができます。 ※リクエスト数は例です。 setUp( scn.inject( Range( 0 , 5 ).map(Math.pow( 2 , _)).toList.flatMap { x => List(heavisideUsers(x.toInt * 10000 ) during ( 30 seconds), nothingFor( 30 seconds))} ).protocols(httpProtocol) ) スパイクを起こさせるのに heavisideUsers を使っています。指定した時間でヘヴィサイドの階段関数を近似するようにリクエストを生成してくれます。 30秒は少し長いように思われるかもしれませんが、実際のユーザ来訪をシミュレートするのでそこまで急激なスパイクよりかは一気にアクセス数が伸びる、というようなシナリオを想定しているためにこうしています。 前編のまとめ Gatlingを使った負荷テストとそれを定期実施するために必要なことをまとめました 正しい負荷テストについて学びました 負荷テストの終了条件を決めました 対象とするメトリクスとその目標数値を決めました 想定する負荷や環境や社内のスキルに合わせて負荷テスト環境を作りました 攻撃に合わせてシナリオを組み立てました 後編では残り4つのポイントを紹介しつつ紹介していく予定です。 お読みいただき、ありがとうございました!
アバター
スマートキャンプ株式会社でデータエンジニアをしている瀧川です。 皆さん!分析SQLを書いていますか!? 弊社ではアプリケーションのログや、各種データをBigQueryに集約しており、諸々の分析をRedashでSQLを書くことで行っています。 Redashは良くも悪くも、SQLですべてを完結する必要があるので、もともと複雑な分析SQLがさらに長くなる...みたいなことありますよね。 そこで私が普段使っている、BigQueryでクエリをDRY(Don't repeat yourself)に書く方法を紹介したい思います! ※ 本記事ではクエリの可読性にフォーカスしているため、パフォーマンスに関しては考慮していません。 ※ 記事内の例はSQLの書き方のみであり、内容に意味はありません。 参考 BigQuery公式Doc 目次 with で結果を一時保存しよう with とサブクエリで定数定義しよう UDF(SQL)を使って共通処理を関数化しよう まとめ with で結果を一時保存しよう 1つ目はすでに常識かもしれませんが、with句を使ってクエリ実行時の一時的な view を作成することができます。 例えば、以下のクエリを見ていただくと、似たようなサブクエリが複数出てきており冗長な気がしますね...! NG #standardSQL select * , ( select count ( 1 ) from `master_table` join `sub_table` using (id)) as cnt from ( select * from `master_table` join `sub_table` using (id)) where id in ( select id from `master_table` join `sub_table` using (id) ) これをwithを使うと以下のようになります。 OK #standardSQL with joined_table as ( select * from `master_table` join `sub_table` using (id) ) select * , ( select count ( 1 ) from joined_table) as cnt from `joined_table` where id in ( select id from `joined_table` ) 一度結果に別名をつけることができるため、意味も伝わりやすくなりますね。 私は以下のように、さらに小さなselectを作ってはviewにしてを繰り返して、最後に組み立てるような書き方をしたりします。 OK #standardSQL with joined_table as ( select * from `master_table` join sub_table using (id) ), count_joined_table as ( select count ( 1 ) as value from `joined_table` ), ids_joined_table as ( select id from `joined_table` ) select * , ( select value from `count_joined_table`) as cnt from `joined_table` where id in `ids_joined_table` with とサブクエリで定数定義しよう クエリ内で何度も使う定数(期間や対象ID)が存在する場合、クエリの修正や値を変更して実行みたいなこと大変になってきます。 (変更漏れでデータがおかしくなることもよくありますね...) NG #standardSQL select * from ( select * from `master_table_*` where parse_date( ' %Y%m%d ' , _table_suffix) between date ( ' 2018-11-01 ' ) and date ( ' 2018-11-30 ' ) ) join ( select * from `sub_table` where created_at between date ( ' 2018-11-01 ' ) and date ( ' 2018-11-30 ' ) and user_id = 1000 ) using (id) そこで、私は以下のように定数だけを管理する view を with で用意して、サブクエリで都度呼び出すようにしています。 これによりparamsの値を変更するだけで、抜け漏れなく更新することができます。 OK #standardSQL with params as ( select date ( ' 2018-11-01 ' ) as start_date, date ( ' 2018-11-30 ' ) as end_date, 1000 as target_user_id ) select * from ( select * from `master_table_*` where ( select parse_date( ' %Y%m%d ' , _table_suffix) between start_date and end_date from `params` ) ) join ( select * from `sub_table` as o where ( select created_at between start_date and end_date and o.user_id = i.target_user_id from `params` as i ) ) using (id) ※ BigQueryにはサブクエリ数に制限があったはずなので、使いすぎると実行できなくなるかも UDF(SQL)を使って共通処理を関数化しよう UDF(UserDefinedFunction)をご存知ですか? 以下のDocumentを見ていただくと、SQLで書くの難しい処理をjavascript使って関数定義できる機能といったイメージかなと思います。 標準 SQL ユーザー定義関数  |  BigQuery  |  Google Cloud 実はドキュメントをよく読むとSQLもUDF化できるので、何度も使っているSQLはUDFにして共通化しましょう! NG #standardSQL select regex_exact(r ' email: (.+) ' , json_extract_scalar(hoge, ' $.hogehoge ' )) as email, regex_exact(r ' tel: (.+) ' , json_extract_scalar(hoge, ' $.hogehoge ' )) as tel, case category when 1 then ' one ' when 2 then ' two ' else ' other ' end as category_title from `master_table` OK #standardSQL create temp function extract_hogehoge(regex string) returns ( regex_exact(regex, json_extract_scalar(hoge, ' $.hogehoge ' )) ), create temp function category2title(category float64) returns ( case category when 1 then ' one ' when 2 then ' two ' else ' other ' end ); select extract_hogehoge(r ' email: (.+) ' ) as email, extract_hogehoge(r ' tel: (.+) ' ) as tel, category2title(category) as category_title from `master_table` まとめ SQLは一般のプログラミング言語から来ると冗長に思いがちですが、モダンなSQLの機能を使うことで(そこそこ)緩和できることが伝われば幸いです。
アバター
あけましておめでとうございます! スマートキャンプ株式会社でエンジニアをしている瀧川と申します。 満を持してこの度エンジニアブログを始めることとなりました。 我々が日々どういった思いや考えで、どういったチャレンジをしているのかを、実例を通してお伝えしていきたいと考えています。 そして、このエンジニアブログを継続することで、社内で技術を精査する文化を作り、読んでいただいた多くの人に価値を届けられるように努力していきます! もし一緒にエンジニア文化を育てていこうといってくださる方がいれば、以下からお申し込みいただきお話させていただければと思います。 どうぞよろしくお願いいたします! smartcamp.asia
アバター