Running Kubernetes Cluster on AWS

Following this guide https://kubernetes.io/docs/getting-started-guides/kops/, set up Kubernetes cluster on AWS using kops.

1. Install kops

According to the guide, kops is:

kops helps you create, destroy, upgrade and maintain production-grade, hig… 続きを読む

Predicting Red Hat Business Value

I. Definition

Project Overview

The history of Open Source project is not so new, however, the faster new technologies are developing and the world is getting globalized, the more people engage in open source projects and the communities are growing. Thanks to the Internet, you are not necessary to be near around the communities to work with them. New innovative ideas are stimulated by people’s interaction across the world and developed as open source projects, e.g. OpenStack, Hadoop and TensorFlow.
But on the other hand, adapting open source products to business operation is not so easy. The development of open source products is very agile and engineers all over the world are always updating the source code. Because of the agility, open source products are always kind of beta version and have some bugs. As a result, there are many companies which offer professional services such as implementation, consulting and maintenance to help businesses utilize open source products.

Today, there are many companies packaging open source products as their service, e.g. Tera Data, HortonWorks, Mirantis and Red Hat. However, they are struggling to scale their business [1]. Even Red Hat, which has been one of the most successful Open Source Platform Business companies since they released their own Linux distribution called “Red Hat Linux” in 1994, the amount of earning is small comparing to other tech giants. For example, Amazon, which started a new cloud service “AWS” a decade ago, earned 12 billion dollars revenue only from AWS in 2016 [2], on the other hand, Red Hat earns just 2 billion dollars revenue from their total business in 2016 [3]. (But the amount is still much bigger than other open source business model based companies).

Problem Statement

Therefore, in order to find ways to maximize their business opportunities, a prediction model is to be created to “accurately identify which customers have the most potential business value for Red Hat” based on their characteristics and activities.”

In the data sets of this project, there are feature characteristics and a corresponding target variable “outcome” which is defined as yes/no binary flag. (It means, a customer’s activity has business value if the outcome of his/her activity is “yes”). Thus, it is necessary to create a binary prediction model which predicts what activities result in positive business outcome or not. As most of the features are categorical data, however, the data should be encoded into numerical values at first. Then, as supervisor data which has several features corresponding with the target “outcome” variable, the data set is passed to binary classification algorithm such as Decision Tree, Logistic Regression and Random Forest. Now, we have no idea which one should be used for this problem, as a result, these algorithms are going to be compared and the most appropriate one is chosen as a base model for the prediction model. When comparing the algorithms, the same metrics would be used to appropriately evaluate their result. After that, by using grid search method, the parameters for the algorithm will be optimized. Then, the prediction model is evaluated again and finally the optimized model is presented.

Metrics

To measure and evaluate the performance of the prediction results, Area under ROC Curve [4] is going to be used as this is a binary classification problem. In ROC curve, true positive rates are plotted against false positive rates. The closer AUC of a model is getting to 1, the better the model is. It means, a model with higher AUC is preferred over those with lower AUC.

II. Analysis

Data Exploration

Data set used in this project contains following data (You can find the data set at Kaggle project page [5]:

  • people.csv: This file contains people list. Each person in the list is unique identified by people_id and has his/her own corresponding characteristics.
  • act_train.csv: This activity file contains unique activities which are identified activity_id. Each activity is corresponding to its own activity characteristics. In the activity file, business value outcome is defined as yes/no for each activity.

Here, there are 8 types of activities. Type 1 activities are different from type 2-7 activities. Type 1 activities are associated with 9 characteristics, on the other hand, type 2-7 activities have only one associated characteristic. And also, in both csv files, except for “date” and “char_38” columns, all data type is categorical so it should be encoded before being processed by binary classification algorithms. Also, as the data sets are separated into 2 csv files, these files should be combined into a merged data set. After the merge, the shape of the training data set is (2197291, 55); 54 predictor features plus 1 target variable.  
Exploratory Visualization
The following chart shows how many activities are performed in each month. According to the chart, “2022/10” has the maximum over 350,000 activities, on the other hand, “2022/07” has the minimum around 50,000 activities. Even though there are some difference in the number of activities among months, it shows no sign of upward trend.

image

Fig. 1 Number of activities per month

The following chart shows how many people joined the community in each month. Even though there are some difference in the number of people join among months, it shows no sign of upward trend. However, as it shows the number of new people join at each month, it means, the number of community member is increasing month by month. According to the data set, the number of people in the data set is 189118 and the average number of people join per month is around 9000. Therefore, this community has more than 50% YoY growth in the number of community member. (It is amazing given the fact most of open source communities are struggling to gather people, however, it is also reasonable to think the similar number of people leave the community as well. But the data set includes no data to investigate it.)

image

Fig. 2 Number of people join per month

The following chart is a very simple overview of the outcome of activities. It shows how many activities result in positive business value (“outcome” = 1) or not. As a result, 44% of activities has positive business impact.

image

Fig. 3 Number of activities per outcome

The following chart is the histogram of community members based on the average outcome of their activities. Maximum is 1, minimum is 0 and the interval is 0.1. What is interesting here is the distribution is completely divided into 2 groups. Some people contribute to the community no matter what activities they do, but on the other hand, some people hardly contribute to the community. Therefore, finding people who has business value for the community is very critical for the prosperity of the community.

image

Fig. 4 Distribution of average outcome by people

In sum, there are 2 things I would like to mention here regarding to the charts. First, this community is a very active community in which constantly new members take part and many activities are performed, moreover, the numbers are really big. So it is reasonable to assume the samples in the dataset are not biased and represent the distribution of Red Hat’s community. Second, the above chart shows whether Red Hat community gains business value or not depends on who performs an activity. Therefore, the issue of this project stated in “Problem Statement” section is an appropriate target to be solved and creating a prediction model is a relevant approach.

Algorithms and Techniques

In order to predict which customers are more valuable for Red Hat business, a binary classification model should be created based on supervised algorithms such as decision tree, logistic regression and random forest tree by using training dataset which includes feature characteristics and a corresponding target variable “outcome” which is defined as yes/no binary flag.

In this project, 3 classification algorithms, Decision Tree [6], Logistic Regression [7] and Random Forest [8] are going to be compared at first. Each of them has its pros and cons, however, all of them can learn much faster than other supervise classification algorithms such as Support Vector Classifier [9]. The dataset of this project has more than 2 million samples with 50+ features, furthermore, 3 algorithms will be compared and grid search method will be used as well in the later phase of this project to optimize parameter. Therefore, choosing CPU and time efficient algorithms is reasonable choice.

These 3 algorithms are widely used for binary classification problem. Logistic Regression gives you a single linear decision boundary which divide your data samples. The pros of this algorithms are low variance and provides probabilities for outcomes, but as the con, it tends to be highly biased. On the other hand, Decision Tree gives you a non-linear decision boundary which divides the feature space into axis-parallel rectangles. This algorithm is good at handling categorical features and also visually intuitive how decision boundary divides your data but likely overfitting. Random Forest is actually an ensemble of decision trees which intends to reduce overfitting. It calculates a large number of decision trees from subsets of the data and combine the trees through a voting process. As a result, Random Forest is less overfitting models but makes it difficult to visually interpret.

Benchmark

This paper is based on the Kaggle project [5] and they set 50% prediction as benchmark. So in this paper, the benchmark is used as well.

III. Methodology

Implementation

The following flow chart shows the overview of the implementation process for creating a binary classification model. In case for reproducing this project, follow the below workflow and refer the code snippets which are cited on this paper.

image

Fig. 5 Overview of implementation flow

Data Preprocessing

As the data sets prepared for this project are separated into 2 csv files, these files should be combined into a merged data set. The action csv file contains all unique activities and each unique activity has a corresponding people ID which shows who performed the activity. Thus, the relation between activity and people ID is one-to-many so that left join method is used for merging datasets on people ID by setting action csv as the left table. The below code is showing very brief overview of this merge implementation.

image

Fig. 6 Code Snippet: Merge people data to activity data

Then, since the most of columns in the dataset is categorical data as described in Data Exploration section, these categorical data should be encoded using label encoding algorithm [10]. Note that the samples in Fig. 7 contains just a few of columns in the dataset. As the dataset actually contains more than 50 features, most of columns are omitted intentionally.

image

Fig. 7 Code Snippet: Encode dataset

image

Fig. 8 Encoded date set samples

Finally, the dataset will be splitted into training and test sets in random order. In this project, 80% of the data will be used for training and 20% for testing.

image

Fig. 9 Code Snippet: Split dataset

Model Selection

In the model selection phase, all parameters of the algorithms compared are set as default. In addition, in order to reduce the time to train algorithms, the training sample size is set to 100,000 samples.

For fitting algorithms and evaluating their predictions, the code below are implemented. Note that, where “classification_algorithm” represents supervised algorithm modules. Also, (features_train, outcome_train, features_test, outcome_test) are corresponding to the variables in Fig. 8 Code Snippet: Splitting dataset.

image

Fig. 10 Code Snippet: Fit and evaluate algorithm

IV. Results

Model Evaluation and Validation

For evaluation, Area under the ROC Curve [11] is used. AUC [4] is the size of area under the plotted curve. The closer AUC of a prediction model is getting to 1, the better the model is. Fig. 10 shows the code snippet for plotting ROC curve based on the prediction results by supervised algorithms. Note that, here “predictions_test” contains prediction results and “outcome_test” is the target score.

image

Fig. 11 Code Snippet: Plot ROC Curve

image

Fig. 12 ROC Curve and AUC of each algorithm 

Refinement

Based on the result of the model selection phase, the most desirable prediction algorithm for this binary classification model is Random Forest, whose AUC value is 0.93.

Next, using grid search method [12], the binary classification model is going to be optimized further. In order to reduce the time for Grid Search, the training sample size is set to 100,000 samples. As optimized parameters, “n_estimator” and “min_sample_leaf” are chosen.

n_estimator: Grid Search parameter set 12, 24, 36, 48, 60
“n_esimator” is the number of trees to calculate. The higher number of trees, the better performance you get, but it makes the model learn slower.

min_samples_leaf: Grid Search parameter set 1, 2, 4, 8, 16
“min_samples_leaf” is the minimum number of samples required to be at a leaf node. The more leaf at a node, the less the algorithm catches noises.

image

Fig. 13 Code Snippet: Grid Search Optimization

image

Fig. 14 Heat Map: Grid Search AUC Score

As a result of Grid Search, the best parameter for the model with the dataset is (“n_estimator”: 60, “min_samples_leaf”: 1). As you can see in Fig. 13 Heat Map, the more “n_estimator” gets bigger, the more the predictions are improved. On the other hand, the increase of “min_samples_leaf” are not effective, or rather, it decreases the prediction performance.

Justification

Finally, there is the optimized parameter set. Therefore, by training the model with the parameter sets and with the all 1,757,832 sample data, the best prediction result should be returned. As a result, the optimized model returned 0.993 AUC score and it is significantly greater than the benchmark (0.5).
In addition, to investigate if the model is robust and generalized enough (it means not overfitting), a new set of prediction results using the classification model was submitted to Kaggle competition (as this paper is based on Kaggle Project). For the submission, another dataset “act_test.csv” provided by Kaggle for the competition is used. Note that “act_test.csv” contains different data from “act_train.csv” which is used for model fitting. As a result, Kaggle evaluated the prediction made by the classification model and returned 0.863 AUC score. Therefore, the binary classification model is generalized enough to make good predictions based on unseen data.

V. Conclusion

Summary

The purpose of this project has been to build a binary classification prediction model for “identify which customers have the most potential business value for Red Hat”. Through the comparison of supervised classification algorithms and the optimization using Grid Search, finally, the best classification models, which is based on Random Forest Classifier, was built and scored 0.993 AUC score which is much higher than the benchmark score 0.5. In sum, using the prediction model, Red Hat can accurately predict which activities performed by its community members likely result in its positive business value. The prediction must be useful for planning its business strategies to maximize their return on investment.
In addition, as the code snippets for implementation is clearly stated throughout this paper, not just for reproducing the model for this specific issue but also it can be applied to similar binary classification issue with some code modification.

VI. Reference

[1] “https://techcrunch.com/2014/02/13/please-dont-tell-me-you-want-to-be-the-next-red-hat/”

[2] “http://phx.corporate-ir.net/phoenix.zhtml?c=97664&p=irol-reportsannual”
[3] “https://investors.redhat.com/news-and-events/press-releases/2016/03-22-2016-201734069”
[4] “http://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html”
[5] “https://www.kaggle.com/c/predicting-red-hat-business-value/data”
[6] “http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html”
[7] “http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html”
[8] “http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html”
[9] “http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html”
[10] “http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html”

[11] “http://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html”
[12] “http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html”

続きを読む

[JAWS-UG CLI] Amazon Kinesis Firehose re:入門 (4) Kinesis Agentによるレコードの登録

この記事について

JAWS-UG CLI専門支部 #90 Kinesis Firehose 復習編で実施するハンズオン用の手順書です。

前提条件

必要な権限

作業にあたっては、以下の権限を有したIAMユーザもしくはIAMロールを利用してください。

  • 以下のサービスに対するフルコントロール権限

    • Kinesis Firehose
    • IAM
    • EC2
    • S3
    • CloudWatch Logs
    • STS
    • (Lambda)
      • データの変換を行う場合
    • (KMS)
      • データの暗号化を行う場合

0. 準備

0.1. リージョンを指定

オレゴンリージョンで実施します。(東京マダー?)

コマンド
export AWS_DEFAULT_REGION="us-west-2"

0.2. 資格情報を確認

コマンド
aws configure list

インスタンスプロファイルを設定したEC2インスタンスでアクセスキーを設定せずに実行した場合、以下のようになります。

結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************QSAA         iam-role
secret_key     ****************c1xY         iam-role
    region                us-west-2              env    AWS_DEFAULT_REGION

0.3. バージョン確認

コマンド
aws --version
結果
aws-cli/1.11.129 Python/2.7.12 Linux/4.9.38-16.33.amzn1.x86_64 botocore/1.5.92

0.4. バージョンアップ(必要に応じて)

コマンド
sudo pip install -U awscli

0.5. 変数の確認

コマンド
cat << ETX

    KEY_MATERIAL_FILE_NAME: ${KEY_MATERIAL_FILE_NAME}
    PUBLIC_IP_ADDRESS: ${PUBLIC_IP_ADDRESS}
    DELIVERY_STREAM_NAME: ${DELIVERY_STREAM_NAME}
    BUCKET_NAME: ${BUCKET_NAME}

ETX

1. ログイン

1.1. SSHで接続

初回接続時、yesを選択

コマンド
ssh -l ec2-user -i ~/.ssh/${KEY_MATERIAL_FILE_NAME} ${PUBLIC_IP_ADDRESS}

2. Kinesis Agentの設定

2.1. 設定ファイルの生成

設定ファイル名の指定

コマンド
AGENT_SETTING_FILE_NAME="agent.json"

デリバリーストリーム名の指定

コマンド
DELIVERY_STREAM_NAME="jawsug-cli-stream"

変数の確認

コマンド
cat << ETX

   AGENT_SETTING_FILE_NAME: ${AGENT_SETTING_FILE_NAME}
   DELIVERY_STREAM_NAME: ${DELIVERY_STREAM_NAME}

ETX
結果

   AGENT_SETTING_FILE_NAME: agent.json
   DELIVERY_STREAM_NAME: jawsug-cli-stream

設定ファイルの生成

コマンド
cat << EOF > ${AGENT_SETTING_FILE_NAME}
{
  "cloudwatch.emitMetrics": true,
  "cloudwatch.endpoint": "https://monitoring.us-west-2.amazonaws.com",
  "firehose.endpoint": "https://firehose.us-west-2.amazonaws.com",
  "flows": [
    {
      "filePattern": "/var/log/httpd/access_log",
      "deliveryStream": "${DELIVERY_STREAM_NAME}",
      "initialPosition": "START_OF_FILE"
    }
  ]
}
EOF

cat ${AGENT_SETTING_FILE_NAME}

JSONファイルの検証

コマンド
jsonlint -q ${AGENT_SETTING_FILE_NAME}

2.2. 設定ファイルの配置

既存ファイルの退避

コマンド
sudo cp /etc/aws-kinesis/agent.json /etc/aws-kinesis/agent.json.org

生成したファイルの配置

コマンド
sudo cp agent.json /etc/aws-kinesis/agent.json

権限の付与

Kinesis-Agentの実行ユーザである「aws-kinesis-agent-user」に対して、
データソースの読み取りおよび実行権限が必要です。

Writing to Amazon Kinesis Firehose Using Amazon Kinesis Agent

コマンド
sudo chmod 755 /var/log/httpd
sudo chmod 644 /var/log/httpd/*

サービスの再起動

コマンド
sudo service aws-kinesis-agent restart

ログオフ

コマンド
exit

3. 動作確認

3.1. Web Serverにアクセス

てきとうにアクセスしてみてください。

そして、1分ほど待ちます。。。

3.2. S3バケット上のファイルを確認

S3バケット上のファイルを確認

コマンド
aws s3 ls --recursive ${BUCKET_NAME}

S3 Bucket上のファイルをダウンロード

コマンド
mkdir data03
cd data03
コマンド
aws s3 sync s3://${BUCKET_NAME} .
結果
download: s3://jawsug-cli-firehose-************/2016/12/19/07/jawsug-cli-stream-1-2016-12-19-07-54-56-47a385b8-4075-44ec-bf15-4f6143b03ddb to 2016/12/19/07/jawsug-cli-stream-1-2016-12-19-07-54-56-47a385b8-4075-44ec-bf15-4f6143b03ddb

S3 Bucket上のファイルを閲覧

コマンド
cat (任意のファイル名)
結果
222.150.28.224 - - [03/Dec/2016:08:05:20 +0000] "GET / HTTP/1.1" 403 3839 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"
222.150.28.224 - - [03/Dec/2016:08:05:21 +0000] "GET / HTTP/1.1" 403 3839 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"

3.3. ファイルの削除

ファイルの削除

コマンド
aws s3 rm --recursive s3://${BUCKET_NAME}/
結果
(省略)
コマンド
cd ..
rm -r data03

以上

続きを読む

DynamoDB は空の値を登録できないけど DynamoDB document client のコンストラクタにオプションを渡すだけで空の値を NULL 型には変換できるよ?

前書き

DynamoDB は空の値を登録しようとすると ValidationException が発生してしまいます。しかし、DynamoDB は NULL 型をサポートしており、DynamoDB document client には空の値を NULL 型に変換するためのオプトイン機能が存在します。

「んなこともう知ってるよハゲ!:anger:

という方は大丈夫なのですが、空の値を代入せずに済む仕様にしたり、空の値 (空文字) をスペースで登録したり、空の値を NULL 型に変換するためのオプトイン機能を自前で実装したり・・・という記事も存在します:scream:

※ オプトイン機能未実装時に投稿された記事も多々有りましたので仕方ありません。

DynamoDB を始める方がそういうものなのかと思ってしまわないようにここでご紹介したいと思います。

どうやるの?

Class: AWS.DynamoDB.DocumentClient — AWS SDK for JavaScript

Constructor Details

new AWS.DynamoDB.DocumentClient(options) ⇒ void

Creates a DynamoDB document client with a set of configuration options.

Options Hash (options):

  • params (map) — An optional map of parameters to bind to every request sent by this service object.
  • service (AWS.DynamoDB) — An optional pre-configured instance of the AWS.DynamoDB service object to use for requests. The object may bound parameters used by the document client.
  • convertEmptyValues (Boolean) — set to true if you would like the document client to convert empty values (0-length strings, binary buffers, and sets) to be converted to NULL types when persisting to DynamoDB.

convertEmptyValues オプションを true にして DynamoDB document client のコンストラクタに渡すだけです:sunglasses:

こちらの Issue により、

jeskew commented on 24 Jan

@brandonmbanks Converting empty values to null is now an opt-in feature of the document client; you can pass a boolean convertEmptyValues option to the document client constructor to have it do so. I’ll open a PR to improve the documentation of this feature.

空の値を null に変換することは document client のオプトイン機能になりました。convertEmptyValues (Boolean) オプションを document client のコンストラクタに渡すことで変換することができます。

こちらの Pull Request が作成され、マージされたことによりオプトイン機能が使えるようになりました:sparkles:

jeskew commented on 14 Dec 2016

This change causes the document client’s marshaller to render empty strings, buffers, and sets as {NULL: true} instead of {S: ''}, {B: new Buffer('')}, {SS: []}, respectively.

/cc @chrisradek

この変更により、document client の marshaller は、空の strings、buffers、および sets を {S: ''}{B: new Buffer('')}{SS: []} の代わりに {NULL: true} としてレンダリングします。

続きを読む

AWS-EC2(Amazon Linux)にGoogleChromeをインストールしてNode+Selenium3でスクリーンショットを取る

はじめに

ブラウザでレンダリングされたスクリーンショットを取る必要があり、EC2にインストールする奮闘した備忘録を残しておきます。今後、他のエンジニアの人が苦悩をしないためにも…。

そもそも

Amazon Linuxが提供しているリポジトリは古い。更に他のリポジトリを入れようと競合する。
本当に厄介でした。ローカルの開発環境はCentOS7で、インストールはすんなりいったので、
AmazonLinuxでも普通にいけるでしょーと思っていた私が間違っていました…。
戦いはこれから始まるのです…

Google Chrome 59のインストールにはgtk3, gdk3, atk, libXssが必要

これがまた厄介。AmazonLinux上でgtk3, gdk3, atk, libXssをインストールする文献が全くもってない。
Ubuntuならともかく、Amazon Linuxの情報がインターネット上にない。
適当にrpm引っ張って入れれば行けるかと思いきや依存関係が多すぎて更にその依存関係を入れようとすると更に依存関係のライブラリが必要になる。考えただけで地獄。

Amazon Linux にはそもそもgtk2, gdk2すら入っていない

そりゃそうですよね。だってCLIで動くことだけを考えて作られてるんだもん。
もうやだこの時点で絶望。

それでも私は戦う道を選んだ

Red Hat Enterpriseに逃げる選択肢もあった。でもお金は極力かけたくない。そんな上からの<社会的フィルター>を叶えるために、私はAmazon LinuxにGoogleChromeというブラウザを入れるための戦い挑んだのでした。

インストールするまえの前置き

結論から言うとGoogleChrome 59を入れるのが間違い。GoogleChromeは58から59に切り替わった時点で、gtk2から、gtk3, gdk2からgdk3に使用するライブラリを切り替えています。
でもgtk2, gdk2ならどうだろう?ワンチャン行けそうな気がしてきた。というわけでインストールするのは現状のstable版の59ではなく58です。(そもそも論としてGoogle ChromeのバイナリのバージョンがSelenium3は58以上じゃないと動かなかったです。)

ソースは私。公開されているGoogle Chrome Version 49から59まで片っ端から入れて確認しました。

とりあえず依存関係を入れてGoogle Chromeを入れる

Amazon LinuxはRHELベースなので、それに合わせてrpmを入れまくる。
(海外のミラーサーバーなので一回失敗すると何度もダウンロードするはめになるので、先にwgetで取得しておく。)


cd /tmp

# atk
$ wget ftp://rpmfind.net/linux/centos/7.3.1611/os/x86_64/Packages/atk-2.14.0-1.el7.x86_64.rpm
$ sudo yum install atk-2.14.0-1.el7.x86_64.rpm

# gtk2 (このバージョンじゃないと後ほどGoogleChromeでライブラリに不足がでているというエラーがでます。)
$ wget ftp://rpmfind.net/linux/centos/6.9/os/x86_64/Packages/gtk2-2.24.23-9.el6.x86_64.rpm
$ sudo yum install gtk2-2.24.23-9.el6.x86_64.rpm

# gtk2-devel
$ wget ftp://rpmfind.net/linux/centos/6.9/os/x86_64/Packages/gtk2-devel-2.24.23-9.el6.x86_64.rpm
$ sudo yum install gtk2-devel-2.24.23-9.el6.x86_64.rpm

# libXss
$ wget ftp://rpmfind.net/linux/centos/7.3.1611/os/x86_64/Packages/libXScrnSaver-1.2.2-6.1.el7.x86_64.rpm
$ sudo yum install libXScrnSaver-1.2.2-6.1.el7.x86_64.rpm

# GoogleChrome 58
$ wget http://orion.lcg.ufrj.br/RPMS/myrpms/google/google-chrome-stable-58.0.3029.110-1.x86_64.rpm
$ sudo yum install google-chrome-stable-58.0.3029.110-1.x86_64.rpm

(記憶が曖昧なので上記の依存関係で解決できるかわかりません…足りないようでしたら教えていただければ情報提供致します。)

これで、Google Chromeのインストールは完了です。これを見つけるのに本当に時間がかかりました。泣きたい。

Selenium Standaloneを落としてくる。

Selenium Standaloneを落としてきます。もちろんjar形式のものです。それ以外には目を向けてはいけません。闇が潜んでいます。
http://docs.seleniumhq.org/download/

スクリーンショット_2017-07-18_5_30_23.png

nvmをインストールしてnodeとnpmを使えるようにする

nvmをインストールします。、

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash

~/.bash_profileに下記を追加します。

[ -s "~/.nvm/nvm.sh" ] && . "~/.nvm/nvm.sh"

その後下記で反映させます。

$ source ~/.bash_profile

nvmでnodeをインストールします。

$ nvm install stable

最新版のnodejsがインストールされるので、それを使うように設定します。

$ nvm use 8.1.4 #インストールされたnodeのバージョン

Seleniumを動かすためのWebdriverとNode用のseleniumをインストールします。


$ cd /path/to # プロジェクトなどのディレクトリ
$ npm install selenium
$ npm install selenium-webdriver
$ npm install chromedriver

あと、Javaと仮想ディスプレイも入れないと

Amazon Linuxにはもちろんディスプレイはついていないので、仮想ディスプレイのXvfbを導入します。
ついでにJavaも入れます。Selenium3の必要要件はJDK1.8以上です。

$ sudo yum install Xvfb java-1.8.0-openjdk java-1.8.0-openjdk-devel GConf2

(注釈: 入れたあとにjava -versionでバージョンを確認してください。1.8.0になっていれば問題ありませんが、それ未満だとSeleniumが動作しませんので、$JAVA_HOMEなどでjdk1.8.0までのパスを適切に通してください。)
(注釈2: 日本語フォントがインストールされていないともしかしたら文字化けする可能性があります。無償利用可能なウェブフォントやAdobeが公開しているSource Han Code JPを入れると幸せになれるかもです。フォントのインストールは割愛します)

ここからが楽しいSeleniumでスクショを取る。

ようやく下準備は整いました。いよいよ Node+Selenium3でスクリーンショットを取ります。
まず、仮想ディスプレイを起動します。

$ Xvfb :90 -ac -screen 0 1024x768x24 > /dev/null 2>&1  &

Selenium3を起動します。(ポートは30000にしています。)

$ DISPLAY=:90 java -Dwebdriver.chrome.driver="node_modules/chromedriver/bin/chromedriver" -jar selenium-server-standalone.jar -port 30000 > /dev/null 2>&1 

スクリーンショットを取るためのNodejsを書きます。以下はサンプルです。

example.js

const webdriver = require('selenium-webdriver');
const chrome = require("selenium-webdriver/chrome");
const fs = require('fs');

const driver = new webdriver.Builder()
        .withCapabilities(
            new chrome.Options()
                .addArguments('--no-sandbox')
                .toCapabilities()
        )
        .usingServer('http://127.0.0.1:30000/wd/hub')
        .build();

driver.get('https://google.co.jp');
driver.takeScreenshot().then((base64Image) => {
    // write file
    fs.writeFile('screenshot.png', Buffer.from(base64Image, 'base64'), 'base64', (error) => {});

}, (reject) => {
});

driver.quit();

example.jsができたところで実際にスクリーンショットを取ってみましょう。

$ node example.js

スクリーンショットを取るとこんな感じに撮れます。
screenshot.png

すごくいい感じ!

youtubeとかだと…
screenshot.png

いい感じにHTML5プレイヤーを認識してくれています。(ちなみに、phantomjsだとだめだった…)

おまけ

おまけとして、スマホ版レイアウトをSeleniumでスクリーンショットを撮りたい場面に遭遇します。
スマホ版レイアウトを採用としているサイトには下記の2種類があります。

  • 1) CSS3のmedia queryを用いて画面サイズによって自動的にレイアウトを変えてくれるサイト
  • 2) User-Agentを見てテンプレートファイルの吐き出しなどを分けているサイト

この両方を満たしてスクリーンショットを撮りたいその願い叶えます。

仮想ディスプレイのサイズをスマートフォンに合わせる

今回はiPhone7 Plusでいきます。

# 先ほどのXvfbを終了させます。
$ sudo pkill -f Xvfb

# 新しく設定
$ Xvfb :90 -ac -screen 0 414x736x24 > /dev/null 2>&1  &

さて、ここまでは 1)を満たすことができました。 次に2)を満たす方法が実は、Chrome自体の起動オプションではっ変更できず、拡張機能でUser-Agentを変更するしかないのです。
ですので、User-Agentを変更するChrome拡張を作成します。
これで完了です。次にこの拡張機能を読み込ませるため、example.jsを修正します。

2017/07/18 19:40追記

下記を追加すれば行けることが判明しました。

.addArguments('--user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3"')
example.js

const webdriver = require('selenium-webdriver');
const chrome = require("selenium-webdriver/chrome");
const fs = require('fs');

const driver = new webdriver.Builder()
        .withCapabilities(
            new chrome.Options()
                .addArguments('--no-sandbox')
                                 .addArguments('--user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3"')
                .toCapabilities()
        )
        .usingServer('http://127.0.0.1:30000/wd/hub')
        .build();

driver.get('https://google.co.jp');
driver.takeScreenshot().then((base64Image) => {
    // write file
    fs.writeFile('screenshot.png', Buffer.from(base64Image, 'base64'), 'base64', (error) => {});

}, (reject) => {
});

driver.quit();

これでスクリーンショットを撮ってみます。
screenshot.png

Youtubeは…
screenshot.png

なんときれいに撮影できました!めでたしめでたし。

続きを読む

crowi-plusをEC2(t2.micro)で動かす

最強を超えた最強のWiki crowi-plus をEC2に入れました。
ほぼDockerで一発ですが、メモリ関係でちょっとつまずいたところがあるので共有として。

EC2インスタンスを立てる

t2.microでOK。
3000番ポートを空けておく。ここを参考
セキュリティ設定する。ここを参考

サーバでコマンドをたくさん打つ

コマンド打つ

$ sudo su
# gitをインストール
$ yum install git
# dockerをインストール
$ yum install -y docker
# docker-composeをインストール
$ curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
# 資材をclone
$ git clone https://github.com/weseek/crowi-plus-docker-compose.git crowi-plus

プラグインを有効化するためにDockerfileをちょっといじる
こんな感じでコメントアウトをはずす

# install plugins if necessary

RUN echo "install plugins" \
  && npm install --save \
     crowi-plugin-lsx \
     crowi-plugin-pukiwiki-like-linker \
  && echo "done."
# you must rebuild if install plugin at least one
RUN npm build:prod

このままだとメモリが足りなくてElasticsearchで落ちるのでスワップの設定をする

$ dd if=/dev/zero of=/swapfile1 bs=2M count=1024
$ chmod 600 /swapfile1
$ mkswap /swapfile1
$ swapon /swapfile1

docker-compose.ymlをいじってポートの変更をする

 ports:
      - 3000:3000

起動する

$ service docker start
$ docker-compose up -d

http://EC2のip:3000でアクセス

続きを読む

Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(後編)

Rails5系で新しく開発しているウェブアプリケーションを、AWSのEC2で稼働している本番環境にCapistranoでデプロイすることになったときのお話です。これまで個人でなにかを開発する際にはRails4系でアプリケーションサーバにUnicornを採用していましたが、今回からはPumaを採用することにしたのでその手順です。前回の手順はこちらです。
Rails4 & Unicorn & Nginx & EC2でサーバー構築

こちらの記事の後編です。
Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(前編)

前提

  • Rails 5.0.0
  • Ruby 2.4.0
  • rbenv 0.4.0
  • Puma
  • Nginx
  • Capistrano 3.7.0
  • Amazon Linux AMI 2017.03.0 (HVM)

手順

  • 1. EC2にRails環境を構築
  • 2. Nginxのインストールと起動
  • 3. Nginxの設定を修正
  • 4. デプロイ設定を修正(ローカル)
  • 5. アプリケーションログを確認
  • 6. データベースを設定
  • 7. アプリケーションサーバを起動
  • 8. デプロイタスクを修正(ローカル)

1. EC2にRails環境を構築

ここまで手順通りに進めている場合にはEC2環境にはまだRailsアプリケーションを動かす環境が整っていないので順番にいれていく。

$ pwd
/home/deploy

1) yum

$ sudo yum update -y

2) ruby-build

$ git clone git://github.com/sstephenson/ruby-build.git
$ cd ruby-build
$ sudo ./install.sh

3) rbenv

$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ exec $SHELL -l

$ rbenv --version
rbenv 1.1.1

4) rbenv-vars

$ git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars

5) Ruby 2.4.0

$ rbenv install 2.4.0
$ rbenv global 2.4.0
$ rbenv rehash

$ ruby -v
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

6) bundler

$ gem install bundler

7) rubyracer

$ gem install therubyracer

8) Rails 5.0.0

$ gem install rails -v 5.0.0

$ rails -v
Rails 5.0.0

9) Puma

$ gem install puma

10) MySQL

$ sudo yum install mysql-devel
$ sudo yum install mysql-server
$ sudo /etc/init.d/mysqld start
$ sudo chkconfig mysqld on

2. Nginxのインストールと起動

まずは本番環境にNginxをインストールする。以下のコマンドで正しく起動できない場合は /var/log/nginx/error.log にエラーログが出力されているはずなので確認する。ブラウザからアクセスしてNginxのwelcome画面が表示されていれば正しく起動できている。

$ sudo yum -y install nginx
$ sudo /etc/init.d/nginx start

設定ファイルはデフォルトでこちら /etc/nginx/nginx.conf が読まれる。初期設定で生成されているファイルは念のためコピーしておくことにする。

$ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.cpy

3. Nginxの設定を修正

Nginxの設定ファイルを修正する。と言っても、デフォルトで生成されたものを何箇所か変更した程度。以下にサンプルを載せておくが自身の環境に応じてよしなに修正する。

$ sudo vi /etc/nginx/nginx.conf
/etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    upstream puma {
        server unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock;
    }
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /var/www/my-app-name/current/public;

        include /etc/nginx/default.d/*.conf;

        location / {
            try_files $uri $uri/index.html $uri.html @webapp;
        }

        location @webapp {
            proxy_read_timeout 300;
            proxy_connect_timeout 300;
            proxy_redirect off;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass http://puma;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

Nginxの設定ファイルの例はこちらに詳しく書かれている。
Nginx configuration example file

4. デプロイ設定を修正(ローカル)

デプロイ対象の本番環境には /var/www/my-app-name/shared/ 配下に以下のディレクトリが並ぶようにする。development環境ではアプリケーションのルート直下にこれらのファイルが存在したが、Capistranoでデプロイした本番環境ではこれらは /shared に配置する。デフォルトではデプロイしない設定になっているため修正する。

  • log
  • piblic
  • tmp

ローカルに戻り、以下の設定を追記し再度デプロイを行う。上記のディレクトリ群が /shared 配下に生成されていることを確認する。

config/puma.rb
app_dir = File.expand_path("../..", __FILE__)
bind "unix://#{app_dir}/tmp/sockets/puma.sock"
pidfile "#{app_dir}/tmp/pids/puma.pid"
state_path "#{app_dir}/tmp/pids/puma.state"
stdout_redirect "#{app_dir}/log/puma.stdout.log", "#{app_dir}/log/puma.stderr.log", true
deploy.rb
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
$ bundle exec cap production deploy

5. アプリケーションログを確認

本番環境に戻りPumaを再起動し、socketファイルやpidファイルが生成されることを確認する。

$ ssh deploy@xx.xxx.xxx.xxx
$ cd /var/www/my-app-name/current

$ bundle exec pumactl start

ブラウザからアクセスし shared/log/ 配下にRailsのアプリケーションログやPumaのログが落ちることを確認する。

$ tail -f shared/log/production.log
$ tail -f shared/log/puma.stderr.log
$ tail -f shared/log/puma.stdout.log

6. データベースを設定

MySQLにrootでログインし新規ユーザを作成する。作成するユーザ名は database.yaml で設定した名前と揃える。

$ mysql -u root

mysql> GRANT ALL PRIVILEGES ON `my-app-name_production`.* TO `my-app-name`@localhost IDENTIFIED BY 'my-app-password';

初期設定では文字コードがlatin1になっているのでutf8に変更していく。character_set_filesystemcharacter_sets_dir 以外をすべて utf8 に設定する。

mysql> show variables like "chara%";
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

設定ファイルに以下の2行を追記して再起動する。

/etc/my.cnf
[mysqld]
character-set-server=utf8

[client]
default-character-set=utf8
$ sudo /etc/init.d/mysqld restart

文字コードが正しくutf8に設定されていることを確認する。問題なければ作成したユーザで rails db:create および rails db:migrate をおこなっておく。

$ bundle exec rails db:create RAILS_ENV=production
$ bundle exec rails db:migrate RAILS_ENV=production

7. アプリケーションサーバを起動

Pumaをproductionモードで起動し表示確認をおこなう。pumaコマンドで起動する場合は -C オプションで設定ファイルを指定しないと読みにいかないので気をつける。また、今回はNginx側の設定でsocketファイル読みにいくように設定してるため -p オプションでポートを指定してしまうとsocketファイルがlistenされなくなるため気をつける。

$ bundle exec puma -t 5:5 -e production -C config/puma.rb

# もしくは

$ bundle exec pumactl start

8. デプロイタスクを修正(ローカル)

デプロイコマンド実行時に関連するタスクを実行するように以下をデプロイタスクに追加する。

1) デプロイ時にbundlerを起動

Gemfile
group :development do
  gem 'capistrano-bundler'
end
Capfile
require "capistrano/bundler"

2) デプロイ時にmigrationを実行

Capfile
require "capistrano/rails/migrations"

3) デプロイ時にPumaを再起動

Gemfile
group :development do
  gem 'capistrano3-puma'
end
Capfile
require 'capistrano/puma'
install_plugin Capistrano::Puma

capistrano3-pumaを利用するための設定ファイル追加する。 puma:config コマンドでローカルで自動生成した設定ファイルをリモートサーバにアップロードできる。

$ bundle exec cap production puma:config

設定ファイルはshared配下に配置され、下記のようなコマンドでローカルからPumaの起動、停止、再起動ができるようになる。

$ bundle exec cap production puma:start
$ bundle exec cap production puma:stop
$ bundle exec cap production puma:restart
$ bundle exec cap production puma:phased-restart

デプロイタスクにPumaの再起動タスクを組み込む。

config/deploy.rb
namespace :deploy do
  desc "Make sure local git is in sync with remote."
  task :confirm do
    on roles(:app) do
      puts "This stage is '#{fetch(:stage)}'. Deploying branch is '#{fetch(:branch)}'."
      puts 'Are you sure? [y/n]'
      ask :answer, 'n'
      if fetch(:answer) != 'y'
        puts 'deploy stopped'
        exit
      end
    end
  end

  desc "Initial Deploy"
  task :initial do
    on roles(:app) do
      before 'deploy:restart', 'puma:start'
      invoke 'deploy'
    end
  end

  desc "Restart Application"
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      invoke 'puma:restart'
    end
  end

  before :starting, :confirm
end

デプロイ実行時にPumaが正しく再起動されていることを確認する。

$ bundle exec cap production deploy

おまけ

エラーログをちゃんと読めばだいたいすぐに解決すると思うが、今回も念のため想定されるエラーと解決方法を載せておく。

1. ディレクトリにアクセスする権限がない

directory index of "/var/www/my-app-name/current/public/" is forbidden, client: yyy.yyy.yy.yyy, server: localhost, request: "GET / HTTP/1.1", host: "xx.xxx.xxx.xxx"

→ locationディレクティブの中にで try_files の記述が足りない。

/etc/nginx/nginx.conf
location / {
    try_files $uri $uri/index.html $uri.html @webapp;
}

location @webapp {
    proxy_read_timeout 300;
    proxy_connect_timeout 300;
    proxy_redirect off;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://puma;
}

2. socketファイルが正しく読めない

connect() to unix:///my-app-name/path/to/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream, client: yyy.yyy.yy.yyy, server: localhost, request: "GET / HTTP/1.1", upstream: "http://unix:///my-app-name/path/to/sockets/puma.sock:/", host: "xx.xxx.xxx.xxx"

→ socketファイルが正しく生成されていない。もしくは生成されているが名前やパスが正しくない。

/var/www/my-app-name/current/config/puma.rb
app_dir = File.expand_path("../..", __FILE__)
bind "unix://#{app_dir}/tmp/sockets/puma.sock"
/var/www/my-app-name/current/config/deploy.rb
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
/etc/nginx/nginx.conf
upstream puma {
    server unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock;
}

location @webapp {
    proxy_pass http://puma;
}

3. socketファイルで通信ができない

connect() to unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock failed (111: Connection refused) while connecting to upstream, client: yyy.yyy.yy.yyy, server: localhost, request: "GET / HTTP/1.1", upstream: "http://unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock:/", host: "xx.xxx.xxx.xxx"

→ Nginxがsocketファイルで通信できなかったよと言っている。以下のようなコマンドでPumaを起動する際 -p オプションでポートを指定すると、デフォルトでsocketファイルをlistenしない仕様になっている。今回のようにsocketファイルで通信をおこないたい場合にはポートの指定はしない。

# NG ->
$ bundle exec puma -t 5:5 -p 3000 -e production -C config/puma.rb

Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.0-p0), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

portの指定を外すと正しくsocketファイルをlistenしてくれる。

# OK ->
$ bundle exec puma -t 5:5 -e production -C config/puma.rb

Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.0-p0), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
* Listening on unix:///var/www/my-app-name/releases/20170620162839/tmp/sockets/puma.sock
Use Ctrl-C to stop
# OK ->
$ bundle exec pumactl start

Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.0-p0), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
* Listening on unix:///var/www/my-app-name/releases/20170620162839/tmp/sockets/puma.sock
Use Ctrl-C to stop

4. MySQLのsocketファイルが読めない

#<Mysql2::Error: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)>

→ MySQLのsocketファイルが見つからないよと言っている。以下のコマンドでsocketファイルのパスを確認し database.yaml のproduction設定に追加する。

$ mysqladmin -u root version
config/database.yaml
production:
  <<: *default
  socket: /var/lib/mysql/mysql.sock

もしくは、MySQLが正しく起動できているにも関わらずこのエラーが出る際にはPumaがdevelopmentモードで起動されており database.yaml のdevelopmentの設定を読みにいっている可能性があるため、正しくproductionモードで起動できているか確認する。

5. secret_key_baseが設定されていない

#<RuntimeError: Missing `secret_key_base` for 'production' environment, set this value in `config/secrets.yml`>

→ 環境変数が正しく読めていない。アプリケーションのルート直下で $ rbenv vars コマンドを実行し が正しく動作しているか確認する。 rbenv: no such command 'vars' とおこられるときは正しくインストールできていない。以下のコマンドでrbenv-varsがインストールされ /var/deploy/.rbenv/plugins 配下に rbenv-vars ディレクトリが生成されることを確認する。

$ git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars
$ rbenv vars

# /var/www/my-app-name/current/.rbenv-vars
export DATABASE_PASSWORD=''
export SECRET_KEY_BASE=''

参考

続きを読む

EC2 Systems ManagerでLinuxのパッチを適用してみた

Linuxの環境に対してPatchを適用できるようになったようです。

Amazon EC2 Systems Manager Now Supports Linux Patching

Previously, Patch Manager only supported Windows managed instances. 
If you wanted to patch Linux managed instances, you needed to use in-house or Linux distribution-specific tools. 
Now, you can manage Linux patches for AWS and on-premises managed instances using the same tool as you do for Windows. 
Just like with Windows patching, Patch Manager lets you automate your Linux patching process using defined auto-approval rules, which lets you only deploy vetted packages. 
This also gives you a single patch compliance view for both your Linux and Windows managed instances.

これまで、Windows Serverに対してはメンテナンスウィンドウやパッチベースラインを利用してパッチの適用ができていましたが、Linuxでも同じ要領でパッチの適用ができるようになったようです。

これは時間の節約ができそうです。早速試します。

Management Consoleを確認

ドキュメント

“AWS-RunPatchBaseline”なるドキュメントが提供されていました。

EC2 Management Console_2.png

パッチベースライン

Amazon Linux、RHEL、Ubuntu用のパッチベースラインが提供されていました。

EC2 Management Console.png

使ってみる

東京リージョンで試します。

コマンド
export AWS_DEFAULT_REGION="ap-northeast-1"

メンテナンスウィンドウの作成

まず、メンテナンスウィンドウを作成します。(これまでと同じです)

コマンド
aws ssm create-maintenance-window 
    --name "LinuxPatching" 
    --no-allow-unassociated-targets 
    --schedule "cron(0 10 10 ? * SAT *)" 
    --duration 2 
    --cutoff 1
結果
{
    "WindowId": "mw-xxxxxxxxxxxxxxxxx"
}

ターゲットとなるLinuxインスタンスの作成

今回はAmazon Linuxを用意します。

ログインするのがめんどいので、UserDataでSSM Agentをインストールします。

また、インターネットにアクセス可能なSubnetに配置しましょう。また、適切なインスタンスプロファイルを設定します。

EC2 Systems Managerの動作要件は、以下のドキュメントを確認してください。

UserData
#!/bin/bash
cd /tmp
sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm

ターゲットの指定

ここもこれまでの手順と変わりません。

コマンド
MAINTENANCE_WINDOW_ID="mw-xxxxxxxxxxxxxxxxx"
INSTANCE_ID="i-xxxxxxxxxxxxxxxxx"
コマンド
aws ssm register-target-with-maintenance-window 
    --window-id ${MAINTENANCE_WINDOW_ID} 
    --resource-type "INSTANCE" 
    --targets "Key=InstanceIds,Values=${INSTANCE_ID}"
結果
{
    "WindowTargetId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

(割愛)パッチベースラインの作成

今回はデフォルトのパッチベースラインと使って動作を確認します。

パッチベースラインの一覧を確認します。

コマンド
aws ssm describe-patch-baselines
結果
{
    "BaselineIdentities": [
        {
            "BaselineName": "AWS-AmazonLinuxDefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline for Amazon Linux Prov                                                                                                                                                            ided by AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-0221829c157d721d8",
            "OperatingSystem": "AMAZON_LINUX"
        },
        {
            "BaselineName": "AWS-DefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline Provided by AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-04ba050f612fba3a6",
            "OperatingSystem": "WINDOWS"
        },
        {
            "BaselineName": "AWS-RedHatDefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline for Redhat Enterprise                                                                                                                                                             Linux Provided by AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-0adf5cb7136a2984d",
            "OperatingSystem": "REDHAT_ENTERPRISE_LINUX"
        },
        {
            "BaselineName": "AWS-UbuntuDefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline for Ubuntu Provided b                                                                                                                                                            y AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-0ec96a11368349171",
            "OperatingSystem": "UBUNTU"
        }
    ]
}

Amazon Linux用のパッチベースラインを確認します。

コマンド
aws ssm get-patch-baseline 
    --baseline-id arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline/pb-0221829c157d721d8
結果
{
    "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline/pb-0221829c157d721d8",
    "Name": "AWS-AmazonLinuxDefaultPatchBaseline",
    "PatchGroups": [],
    "RejectedPatches": [],
    "GlobalFilters": {
        "PatchFilters": [
            {
                "Values": [
                    "AmazonLinux2012.03",
                    "AmazonLinux2012.09",
                    "AmazonLinux2013.03",
                    "AmazonLinux2013.09",
                    "AmazonLinux2014.03",
                    "AmazonLinux2014.09",
                    "AmazonLinux2015.03",
                    "AmazonLinux2015.09",
                    "AmazonLinux2016.03",
                    "AmazonLinux2016.09",
                    "AmazonLinux2017.03",
                    "AmazonLinux2017.09"
                ],
                "Key": "PRODUCT"
            }
        ]
    },
    "ApprovalRules": {
        "PatchRules": [
            {
                "PatchFilterGroup": {
                    "PatchFilters": [
                        {
                            "Values": [
                                "Security"
                            ],
                            "Key": "CLASSIFICATION"
                        },
                        {
                            "Values": [
                                "Critical",
                                "Important"
                            ],
                            "Key": "SEVERITY"
                        }
                    ]
                },
                "ApproveAfterDays": 7,
                "ComplianceLevel": "UNSPECIFIED"
            },
            {
                "PatchFilterGroup": {
                    "PatchFilters": [
                        {
                            "Values": [
                                "Bugfix"
                            ],
                            "Key": "CLASSIFICATION"
                        }
                    ]
                },
                "ApproveAfterDays": 7,
                "ComplianceLevel": "UNSPECIFIED"
            }
        ]
    },
    "ModifiedDate": 1499203527.709,
    "CreatedDate": 1499203527.709,
    "ApprovedPatchesComplianceLevel": "UNSPECIFIED",
    "OperatingSystem": "AMAZON_LINUX",
    "ApprovedPatches": [],
    "Description": "Default Patch Baseline for Amazon Linux Provided by AWS."
}

タスクを指定

使用するドキュメントを確認します。

コマンド
DOCUMENT_NAME="AWS-RunPatchBaseline"

aws ssm describe-document 
    --name ${DOCUMENT_NAME}
結果
{
    "Document": {
        "Status": "Active",
        "Hash": "d5c29590f323c144ce0338b8424970e2284f780b404dc164b2bc96dae5415218",
        "Name": "AWS-RunPatchBaseline",
        "Parameters": [
            {
                "Type": "String",
                "Name": "Operation",
                "Description": "(Required) The update or configuration to perform on the instance. The system checks if patches specified in the patch baseline are installed on the instance. The install operation installs patches missing from the baseline."
            },
            {
                "DefaultValue": "",
                "Type": "String",
                "Name": "SnapshotId",
                "Description": "(Optional) The snapshot ID to use to retrieve a patch baseline snapshot."
            }
        ],
        "DocumentType": "Command",
        "PlatformTypes": [
            "Linux"
        ],
        "DocumentVersion": "1",
        "HashType": "Sha256",
        "CreatedDate": 1499451553.857,
        "Owner": "Amazon",
        "SchemaVersion": "2.2",
        "DefaultVersion": "1",
        "LatestVersion": "1",
        "Description": "Scans for or installs patches from a patch baseline to a Linux or Windows operating system."
    }
}

タスクを設定する際には、IAMロールを指定する必要があります。

Configuring Roles and Permissions for Maintenance Windows

コマンド
ROLE_ARN_FOR_MAINTENANCE_WINDOW="arn:aws:iam::XXXXXXXXXXXX:role/AmazonSSMMaintenanceWindowRole"

また、実行結果を格納するS3バケットも予め用意しておきます。

コマンド
BUCKET_NAME="XXXXXXXX"
PREFIX="logs"

PATCH_BASELINE_LOGGING_FILE_NAME="patch_baseline_logging.json"

cat << EOF > ${PATCH_BASELINE_LOGGING_FILE_NAME}
{
    "S3BucketName": "${BUCKET_NAME}",
    "S3KeyPrefix": "${PREFIX}",
    "S3Region": "${AWS_DEFAULT_REGION}"
}
EOF

その他のパラメーターを確認し、タスクを登録します。

コマンド
PATCH_BASELINE_PARAMETER_FILE_NAME="patch_baseline_parameter.json"

cat << EOF > ${PATCH_BASELINE_PARAMETER_FILE_NAME}
{
    "Operation": {
        "Values": [
            "Install"
        ]
    }
}
EOF
コマンド
TASK_TYPE="RUN_COMMAND"
PRIORITY="1"
MAX_COCCURRENCY="1"
MAX_ERRORS="1"
コマンド
aws ssm register-task-with-maintenance-window 
    --window-id ${MAINTENANCE_WINDOW_ID} 
    --targets "Key=InstanceIds,Values=${INSTANCE_ID}" 
    --task-arn ${DOCUMENT_NAME} 
    --service-role-arn ${ROLE_ARN_FOR_MAINTENANCE_WINDOW} 
    --task-type ${TASK_TYPE} 
    --task-parameters file://${PATCH_BASELINE_PARAMETER_FILE_NAME} 
    --priority ${PRIORITY} 
    --max-concurrency ${MAX_COCCURRENCY} 
    --max-errors ${MAX_ERRORS} 
    --logging-info file://${PATCH_BASELINE_LOGGING_FILE_NAME}
結果
{
    "WindowTaskId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

実行結果の確認

メンテナンスウィンドウで指定した時刻になるまで待ちます。

細かいところをとばして、S3に出力された実行結果を確認します。

結果
/usr/bin/python
Loaded plugins: priorities, update-motd, upgrade-helper
Resolving Dependencies
--> Running transaction check
---> Package python26-requests.noarch 0:1.2.3-5.10.amzn1 will be installed
--> Processing Dependency: python(abi) = 2.6 for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26-urllib3 >= 1.7 for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26(dist-packages) for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26-chardet for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26-ordereddict for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Running transaction check
---> Package python26.x86_64 0:2.6.9-2.88.amzn1 will be installed
--> Processing Dependency: libpython2.6.so.1.0()(64bit) for package: python26-2.6.9-2.88.amzn1.x86_64
---> Package python26-chardet.noarch 0:2.0.1-7.7.amzn1 will be installed
---> Package python26-urllib3.noarch 0:1.8.2-1.5.amzn1 will be installed
--> Processing Dependency: python26-backports-ssl_match_hostname for package: python26-urllib3-1.8.2-1.5.amzn1.noarch
--> Processing Dependency: python26-six for package: python26-urllib3-1.8.2-1.5.amzn1.noarch
--> Running transaction check
---> Package python26-backports-ssl_match_hostname.noarch 0:3.4.0.2-1.12.amzn1 will be installed
--> Processing Dependency: python26-backports for package: python26-backports-ssl_match_hostname-3.4.0.2-1.12.amzn1.noarch
---> Package python26-libs.x86_64 0:2.6.9-2.88.amzn1 will be installed
---> Package python26-six.noarch 0:1.8.0-1.23.amzn1 will be installed
--> Running transaction check
---> Package python26-backports.x86_64 0:1.0-3.14.amzn1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package                              Arch   Version            Repository
                                                                           Size
================================================================================
Installing:
 python26-requests                    noarch 1.2.3-5.10.amzn1   amzn-main  94 k
Installing for dependencies:
 python26                             x86_64 2.6.9-2.88.amzn1   amzn-main 5.8 M
 python26-backports                   x86_64 1.0-3.14.amzn1     amzn-main 5.2 k
 python26-backports-ssl_match_hostname
                                      noarch 3.4.0.2-1.12.amzn1 amzn-main  12 k
 python26-chardet                     noarch 2.0.1-7.7.amzn1    amzn-main 377 k
 python26-libs                        x86_64 2.6.9-2.88.amzn1   amzn-main 697 k
 python26-six                         noarch 1.8.0-1.23.amzn1   amzn-main  31 k
 python26-urllib3                     noarch 1.8.2-1.5.amzn1    amzn-main  98 k

Transaction Summary
================================================================================
Install  1 Package (+7 Dependent packages)

Total download size: 7.0 M
Installed size: 22 M
Downloading packages:
--------------------------------------------------------------------------------
Total                                              1.9 MB/s | 7.0 MB  00:03     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : python26-2.6.9-2.88.amzn1.x86_64                             1/8 
  Installing : python26-libs-2.6.9-2.88.amzn1.x86_64                        2/8 
  Installing : python26-chardet-2.0.1-7.7.amzn1.noarch                      3/8 
  Installing : python26-six-1.8.0-1.23.amzn1.noarch                         4/8 
  Installing : python26-backports-1.0-3.14.amzn1.x86_64                     5/8 
  Installing : python26-backports-ssl_match_hostname-3.4.0.2-1.12.amzn1.n   6/8 
  Installing : python26-urllib3-1.8.2-1.5.amzn1.noarch                      7/8 
  Installing : python26-requests-1.2.3-5.10.amzn1.noarch                    8/8 
  Verifying  : python26-requests-1.2.3-5.10.amzn1.noarch                    1/8 
  Verifying  : python26-chardet-2.0.1-7.7.amzn1.noarch                      2/8 
  Verifying  : python26-urllib3-1.8.2-1.5.amzn1.noarch                      3/8 
  Verifying  : python26-libs-2.6.9-2.88.amzn1.x86_64                        4/8 
  Verifying  : python26-six-1.8.0-1.23.amzn1.noarch                         5/8 
  Verifying  : python26-2.6.9-2.88.amzn1.x86_64                             6/8 
  Verifying  : python26-backports-1.0-3.14.amzn1.x86_64                     7/8 
  Verifying  : python26-backports-ssl_match_hostname-3.4.0.2-1.12.amzn1.n   8/8 

Installed:
  python26-requests.noarch 0:1.2.3-5.10.amzn1                                   

Dependency Installed:
  python26.x86_64 0:2.6.9-2.88.amzn1                                            
  python26-backports.x86_64 0:1.0-3.14.amzn1                                    
  python26-backports-ssl_match_hostname.noarch 0:3.4.0.2-1.12.amzn1             
  python26-chardet.noarch 0:2.0.1-7.7.amzn1                                     
  python26-libs.x86_64 0:2.6.9-2.88.amzn1                                       
  python26-six.noarch 0:1.8.0-1.23.amzn1                                        
  python26-urllib3.noarch 0:1.8.2-1.5.amzn1                                     

Complete!
07/08/2017 10:30:55 root [INFO]: Attempting to acquire instance information from ssm-cli.
07/08/2017 10:30:55 root [INFO]: ssm-cli path is: /usr/bin/ssm-cli.
07/08/2017 10:30:55 root [INFO]: Instance metadata from ssm-cli is: {"instance-id":"i-xxxxxxxxxxxxxxxxx","region":"ap-northeast-1","release-version":"2.0.847.0"}.
07/08/2017 10:30:55 urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): s3.dualstack.ap-northeast-1.amazonaws.com
07/08/2017 10:30:56 urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): s3.dualstack.ap-northeast-1.amazonaws.com
07/08/2017 10:30:57 root [INFO]: Attempting to acquire instance information from ssm-cli.
07/08/2017 10:30:57 root [INFO]: ssm-cli path is: /usr/bin/ssm-cli.
07/08/2017 10:30:57 root [INFO]: Instance metadata from ssm-cli is: {"instance-id":"i-xxxxxxxxxxxxxxxxx","region":"ap-northeast-1","release-version":"2.0.847.0"}.
07/08/2017 10:30:57 root [INFO]: Operation type: Install.
07/08/2017 10:30:57 root [INFO]: Snapshot ID: e62016f7-257d-4aee-b520-b58b10ad3cdd.
07/08/2017 10:30:57 root [INFO]: Instance ID: i-xxxxxxxxxxxxxxxxx.
07/08/2017 10:30:57 root [INFO]: Region ID: ap-northeast-1.
07/08/2017 10:30:57 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
07/08/2017 10:30:57 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
07/08/2017 10:30:57 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): ssm.ap-northeast-1.amazonaws.com
07/08/2017 10:30:57 root [INFO]: Product: AmazonLinux2017.03.
07/08/2017 10:30:57 root [INFO]: Snapshot download URL: https://patch-baseline-snapshot-ap-northeast-1.s3-ap-northeast-1.amazonaws.com/325e6910d69bd8861bea653821b277cecf2df85952414e32ec904b9a32a4a88b-788063364413/AMAZON_LINUX-e62016f7-257d-4aee-b520-b58b10ad3cdd?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170708T103057Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAILTSCHNHVBZOKOSA%2F20170708%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=833c5f22664d48aa9b6fe5a4db9d04ee878293efb56d3bd581e1e8f0ce421de7.
07/08/2017 10:30:57 urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): patch-baseline-snapshot-ap-northeast-1.s3-ap-northeast-1.amazonaws.com
07/08/2017 10:30:57 root [INFO]: Patch baseline: {u'baselineId': u'pb-0221829c157d721d8', u'name': u'AWS-AmazonLinuxDefaultPatchBaseline', u'modifiedTime': 1499203527.709, u'description': u'Default Patch Baseline for Amazon Linux Provided by AWS.', u'rejectedPatches': [], u'globalFilters': {u'filters': [{u'values': [u'AmazonLinux2012.03', u'AmazonLinux2012.09', u'AmazonLinux2013.03', u'AmazonLinux2013.09', u'AmazonLinux2014.03', u'AmazonLinux2014.09', u'AmazonLinux2015.03', u'AmazonLinux2015.09', u'AmazonLinux2016.03', u'AmazonLinux2016.09', u'AmazonLinux2017.03', u'AmazonLinux2017.09'], u'key': u'PRODUCT'}]}, u'approvalRules': {u'rules': [{u'filterGroup': {u'filters': [{u'values': [u'Security'], u'key': u'CLASSIFICATION'}, {u'values': [u'Critical', u'Important'], u'key': u'SEVERITY'}]}, u'complianceLevel': u'UNSPECIFIED', u'approveAfterDays': 7}, {u'filterGroup': {u'filters': [{u'values': [u'Bugfix'], u'key': u'CLASSIFICATION'}]}, u'complianceLevel': u'UNSPECIFIED', u'approveAfterDays': 7}]}, u'createdTime': 1499203527.709, u'approvedPatchesComplianceLevel': u'UNSPECIFIED', u'operatingSystem': u'AMAZON_LINUX', u'approvedPatches': [], u'accountId': u'486716784251'}.
07/08/2017 10:30:57 root [INFO]: Patch group: .
07/08/2017 10:30:57 root [INFO]: Operating system: AMAZON_LINUX.
07/08/2017 10:30:57 root [WARNING]: Unable to gain necessary access for possible kernel updates, code: 1.
07/08/2017 10:30:58 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
07/08/2017 10:30:58 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
Loaded plugins: priorities, update-motd, upgrade-helper
07/08/2017 10:30:58 root [INFO]: No updates, skipping.
Loaded plugins: priorities, update-motd, upgrade-helper
07/08/2017 10:30:59 root [INFO]: 
Patch compliance initialized with instance ID:i-xxxxxxxxxxxxxxxxx, 
baseline ID: pb-0221829c157d721d8, snapshot ID: e62016f7-257d-4aee-b520-b58b10ad3cdd, patch group: ,
start time: 2017-07-08 10:30:58.177869, end time: 2017-07-08 10:30:59.590999, upload NA compliance: False

07/08/2017 10:30:59 root [INFO]: Start to upload patch compliance.
07/08/2017 10:30:59 root [INFO]: Summary: {'ContentHash': 'b71836e461121012e37c8f25eb2846ade95a267c9fc237d5e5af36deaf8048f8', 'TypeName': 'AWS:PatchSummary', 'SchemaVersion': '1.0', 'CaptureTime': '2017-07-08T10:30:59Z', 'Content': [{'OperationStartTime': '2017-07-08T10:30:58Z', 'FailedCount': '0', 'PatchGroup': u'', 'OperationType': u'Install', 'BaselineId': u'pb-0221829c157d721d8', 'MissingCount': '0', 'NotApplicableCount': '249', 'OperationEndTime': '2017-07-08T10:30:59Z', 'InstalledOtherCount': '377', 'SnapshotId': u'e62016f7-257d-4aee-b520-b58b10ad3cdd', 'InstalledCount': '20'}]}
07/08/2017 10:30:59 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): ssm.ap-northeast-1.amazonaws.com
07/08/2017 10:31:00 root [INFO]: Upload complete.
07/08/2017 10:31:00 root [INFO]: Report upload is successful.

感想

動作確認をどうするのかとか、適用を除外したい/別途ラインでは該当しないけど適用したいなどの例外処理(パッチベースラインで設定可能)をどうするのかなど、予め考えていくことはありますが、できるだけ仕事しなくていいようにがんばりましょう!

続きを読む

WebpackでAWS SDKをバンドルするとサイズが大きいのでダイエットする

経緯

サーバレス、流行ってますね。
ウェブホスティングしたS3上にReactやRiot.jsなどでSPAを作って、認証はCognito UserPoolというのがテッパン構成でしょう。

そして、SPAを作るならWebpackでES6で書いたソースをトランスパイルして、バンドル(ひとつの*.jsファイルにまとめること)して…というのが通例ですね。(サーバーサイドの人にはこの辺がツラい)

しかしながら、いざやってみると…でかいよバンドルされたJSファイル。minifyして2MB近くとか…。

何が大きいのかwebpack-bundle-size-analyzerを使って見てみましょう。

$ $(npm bin)/webpack --json | webpack-bundle-size-analyzer 
aws-sdk: 2.2 MB (79.8%)
lodash: 100.65 KB (3.56%)
amazon-cognito-identity-js: 94.85 KB (3.36%)
node-libs-browser: 84.87 KB (3.00%)
  buffer: 47.47 KB (55.9%)
  url: 23.08 KB (27.2%)
  punycode: 14.33 KB (16.9%)
  <self>: 0 B (0.00%)
riot: 80.64 KB (2.85%)
jmespath: 56.94 KB (2.02%)
xmlbuilder: 47.82 KB (1.69%)
util: 16.05 KB (0.568%)
  inherits: 672 B (4.09%)
  <self>: 15.4 KB (95.9%)
crypto-browserify: 14.8 KB (0.524%)
riot-route: 8.92 KB (0.316%)
debug: 8.91 KB (0.316%)
events: 8.13 KB (0.288%)
uuid: 5.54 KB (0.196%)
process: 5.29 KB (0.187%)
querystring-es3: 5.06 KB (0.179%)
obseriot: 4.43 KB (0.157%)
<self>: 27.78 KB (0.983%)

なるほど、AWS SDKですね、やっぱり。

なぜ大きくなっちゃうのか

Webpackによるバンドルではnpm installしたモジュールが全て闇雲にバンドルされるわけではありません。
結構賢くて実際に使われているものだけがバンドルされるように考えられています。具体的にはimport(require)されているモジュールだけを再帰的にたどって1つのJSファイルにまとめていきます。
つまり、importしているファイル数が多ければ多いほどバンドルされたファイルは大きくなります。

import AWS from "aws-sdk"

普通のサンプルだとAWS SDKを使うときはこのようにimportすると思います。これが大きな罠なのです。
AWS SDKのソースを見てみましょう。

lib/aws.js
// Load all service classes
require('../clients/all');

ここがサイズを肥大化させる要因となっています。全てのモジュールを読み込んでいるので使いもしないAWSの機能も含めて全て読み込んでしまっているのです。

どうしたらダイエットできるのか

http://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/webpack.html#webpack-importing-services

を見てみると書いてあります。

The require() function specifies the entire SDK. A webpack bundle generated with this code would include the full SDK but the full SDK is not required when only the Amazon S3 client class is used.

require("aws-sdk")と書いてしまうと、たとえS3しか使わなくても全部の機能を読み込んでしまいます。
注) import from "aws-sdk"と書いても同じ意味になります。

その下の方に、

Here is what the same code looks like when it includes only the Amazon S3 portion of the SDK.

// Import the Amazon S3 service client
var S3 = require('aws-sdk/clients/s3');

// Set credentials and region
var s3 = new S3({
    apiVersion: '2006-03-01',
    region: 'us-west-1', 
    credentials: {YOUR_CREDENTIALS}
  });

と答えが載っています。

clientsディレクトリ以下のファイルを個別にimportすることができるようになっているということです。

なるほど、試してみた

https://github.com/NewGyu/riot-sample/compare/cognito-login…fat-cognito-login

$ $(npm bin)/webpack --json | webpack-bundle-size-analyzer 
aws-sdk: 325.01 KB (36.3%)
lodash: 100.65 KB (11.2%)
amazon-cognito-identity-js: 94.85 KB (10.6%)
 :

2.2MB -> 325KB と激ヤセです!

続きを読む

CloudFrontでモバイル端末を判別する

サーバー側で User-Agent を見て端末を判別するとキャッシュ効率が落ちるので、代わりに下記の4つのHTTPヘッダをWhitelistに入れると、判別済みの情報が送られてきます。

  • CloudFront-Is-Desktop-Viewer
  • CloudFront-Is-Mobile-Viewer
  • CloudFront-Is-SmartTV-Viewer
  • CloudFront-Is-Tablet-Viewer

これらの動作を調べてみました。

iOSスマートフォン (iPhone)

User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1

Desktop Mobile SmartTV Tablet
false true false false

iOSタブレット (iPad)

User-Agent: Mozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1

Desktop Mobile SmartTV Tablet
false true false true

MobileとTablet両方trueになる

Mac (Firefox)

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0

Desktop Mobile SmartTV Tablet
true false false false

Androidスマートフォン (Nexus 5X)

User-Agent: Mozilla/5.0 (Linux; Android 7.1.1; Nexus 5X Build/N4F26I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36

Desktop Mobile SmartTV Tablet
false true false false

Androidタブレット (Nexus 7)

User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MMB29O) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Safari/537.36

Desktop Mobile SmartTV Tablet
false true false true

ちゃんとスマートフォンとタブレット判別できてる!

Fire TV

User-Agent: Mozilla/5.0 (Linux; U; Android 4.2.2; en-us; AFTB Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30

Desktop Mobile SmartTV Tablet
false false true false

検証に用いたソースコード

PHPで殴り書きしました。

<?php
echo($_SERVER['HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER']);
echo($_SERVER['HTTP_CLOUDFRONT_IS_MOBILE_VIEWER']);
echo($_SERVER['HTTP_CLOUDFRONT_IS_SMARTTV_VIEWER']);
echo($_SERVER['HTTP_CLOUDFRONT_IS_TABLET_VIEWER']);

参考文献

続きを読む