Event Ruler (簡潔にするためにドキュメントの残りの部分では Ruler と呼ばれます) は、 Rules をEventsに照合できるようにする Java ライブラリです。イベントはフィールドのリストであり、名前と値のペアまたは JSON オブジェクトとして指定できます。ルールは、イベント フィールド名を可能な値のリストに関連付けます。 Ruler を使用する理由は 2 つあります。
コンテンツ:
例を挙げて説明するのが最も簡単です。
イベントは JSON オブジェクトです。以下に例を示します。
{
"version" : "0" ,
"id" : "ddddd4-aaaa-7777-4444-345dd43cc333" ,
"detail-type" : "EC2 Instance State-change Notification" ,
"source" : "aws.ec2" ,
"account" : "012345679012" ,
"time" : "2017-10-02T16:24:49Z" ,
"region" : "us-east-1" ,
"resources" : [
"arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000"
] ,
"detail" : {
"c-count" : 5 ,
"d-count" : 3 ,
"x-limit" : 301.8 ,
"source-ip" : "10.0.0.33" ,
"instance-id" : "i-000000aaaaaa00000" ,
"state" : "running"
}
}
これを名前と値のペアのセットとして見ることもできます。簡潔にするために、サンプルのみを紹介します。 Ruler には、JSON 形式と名前/値のペアの両方でイベントを提供するための API があります。
+--------------+------------------------------------------+
| name | value |
|--------------|------------------------------------------|
| source | "aws.ec2" |
| detail-type | "EC2 Instance State-change Notification" |
| detail.state | "running" |
+--------------+------------------------------------------+
JSON フォームのイベントは、生の JSON 文字列、または解析された Jackson JsonNode の形式で提供される場合があります。
このセクションのルールはすべて、上記のサンプル イベントと一致します。
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
これは、イベント内の他のフィールドを無視して、 resource
、 detail-type
、およびdetail.state
値に指定された値を持つすべてのイベントと一致します。これは、 detail.state
の値が"initializing"
であった場合にも一致します。
ルールの値は常に配列として提供され、イベントの値が配列で提供された値の 1 つである場合に一致します。 resources
への参照は、イベント内の値も配列である場合、イベント配列とルール配列の共通部分が空でない場合にルールが一致することを示しています。
{
"time" : [ { "prefix" : "2017-10-02" } ]
}
接頭辞一致は文字列値フィールドでのみ機能します。
{
"source" : [ { "prefix" : { "equals-ignore-case" : "EC2" } } ]
}
プレフィックスのequals-ignore-case一致は文字列値フィールドでのみ機能します。
{
"source" : [ { "suffix" : "ec2" } ]
}
サフィックス一致は文字列値フィールドでのみ機能します。
{
"source" : [ { "suffix" : { "equals-ignore-case" : "EC2" } } ]
}
接尾辞のequals-ignore-case一致は文字列値フィールドでのみ機能します。
{
"source" : [ { "equals-ignore-case" : "EC2" } ]
}
Equals-ignore-case 一致は文字列値フィールドでのみ機能します。
{
"source" : [ { "wildcard" : "Simple*Service" } ]
}
ワイルドカード一致は文字列値フィールドでのみ機能します。 1 つの値には 0 から多数のワイルドカード文字を含めることができますが、連続したワイルドカード文字は許可されません。特にアスタリスク文字と一致させるには、ワイルドカード文字をバックスラッシュでエスケープできます。 2 つの連続するバックスラッシュ (つまり、バックスラッシュでエスケープされたバックスラッシュ) は、実際のバックスラッシュ文字を表します。アスタリスクまたはバックスラッシュ以外の文字をエスケープするバックスラッシュは許可されません。
Anything but match はその名のとおり、ルールで指定されているもの以外のすべてに一致します。
Anything-but は、単一の文字列と数値またはリストで動作します。これらには、完全に文字列または完全に数値が含まれている必要があります。また、文字列または文字列のリストの接頭辞、接尾辞、または大文字と小文字の一致を無視する場合にも適用できます。
単一以外 (文字列、次に数値):
{
"detail" : {
"state" : [ { "anything-but" : "initializing" } ]
}
}
{
"detail" : {
"x-limit" : [ { "anything-but" : 123 } ]
}
}
リスト以外のすべて (文字列):
{
"detail" : {
"state" : [ { "anything-but" : [ "stopped" , "overloaded" ] } ]
}
}
リスト (数字) 以外のすべて:
{
"detail" : {
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ]
}
}
プレフィックス以外の何もの:
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : "init" } } ]
}
}
プレフィックス リスト (文字列) 以外のすべて:
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : [ "init" , "error" ] } } ]
}
}
サフィックス以外のもの:
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : "1234" } } ]
}
}
サフィックス以外のリスト (文字列):
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : [ "1234" , "6789" ] } } ]
}
}
大文字と小文字を区別しない場合:
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : "Stopped" } } ]
}
}
大文字と小文字を区別しないものリスト (文字列):
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : [ "Stopped" , "OverLoaded" ] } } ]
}
}
ワイルドカード以外のもの:
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : "*/bin/*.jar" } } ]
}
}
ワイルドカード以外のリスト (文字列):
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : [ "*/bin/*.jar" , "*/bin/*.class" ] } } ]
}
}
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ] ,
"d-count" : [ { "numeric" : [ "<" , 10 ] } ] ,
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
上記のc-count
、 d-count
、およびx-limit
への参照は数値の一致を示しており、JSON 数値の値でのみ機能します。数値マッチングは、IEEE 754 binary64
標準を実装する Java のdouble
プリミティブと同じ精度と範囲をサポートします。
{
"detail" : {
"source-ip" : [ { "cidr" : "10.0.0.0/24" } ]
}
}
これは IPv6 アドレスでも機能します。
Exists マッチングは、JSON イベント内のフィールドの有無に基づいて機能します。
以下のルールは、detail.c-count フィールドが存在するすべてのイベントに一致します。
{
"detail" : {
"c-count" : [ { "exists" : true } ]
}
}
以下のルールは、detail.c-count フィールドを持たないあらゆるイベントに一致します。
{
"detail" : {
"c-count" : [ { "exists" : false } ]
}
}
注: Exists
match はリーフ ノードでのみ機能します。中間ノードでは機能しません。
たとえば、上の例のexists : false
以下のイベントと一致します。
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
ただし、 c-count
リーフ ノードではないため、以下のイベントとも一致します。
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
"c-count" : {
"c1" : 100
}
}
}
{
"time" : [ { "prefix" : "2017-10-02" } ] ,
"detail" : {
"state" : [ { "anything-but" : "initializing" } ] ,
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ] ,
"d-count" : [ { "numeric" : [ "<" , 10 ] } ] ,
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ] ,
"source-ip" : [ { "cidr" : "10.0.0.0/8" } ]
}
}
上記の例が示すように、Ruler は、ルールで指定されたすべてのフィールドが一致する場合にルールが一致するとみなし、指定されたフィールド値のいずれかが一致する場合、つまり Ruler が「And」ロジックを適用した場合にフィールドが一致するとみなします。デフォルトでは、「And」プリミティブなしですべてのフィールドに適用されます。
「Or」効果に到達するには 2 つの方法があります。
「$or」プリミティブにより、顧客はルール内のフィールド間の「または」関係を直接記述することができます。
ルーラーは、ルールが以下のすべての条件を満たしている場合にのみ、「または」関係を認識します。
/src/main/software/amazon/event/ruler/Constants.java#L38
event/ruler/Constants.java#L38 の RESERVED_FIELD_NAMES_IN_OR_RELATIONSHIP を参照してください。以下のルールは「」として解析されません。 「数値」と「プレフィックス」はルーラーの予約キーワードであるため、「OR」関係。 {
"$or": [ {"numeric" : 123}, {"prefix": "abc"} ]
}
それ以外の場合、ルーラーは「$or」をルール内の他の文字列と同じ通常のファイル名として扱うだけです。
通常の「または」:
// Effect of "source" && ("metricName" || "namespace")
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
]
}
並列「または」:
// Effect of ("metricName" || "namespace") && ("detail.source" || "detail.detail-type")
{
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
] ,
"detail" : {
"$or" : [
{ "source" : [ "aws.cloudwatch" ] } ,
{ "detail-type" : [ "CloudWatch Alarm State Change" ] }
]
}
}
「または」の中に「そして」が入っています
// Effect of ("source" && ("metricName" || ("metricType && "namespace") || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
} ,
{ "scope" : [ "Service" ] }
]
}
入れ子になった「または」と「そして」
// Effect of ("source" && ("metricName" || ("metricType && "namespace" && ("metricId" || "spaceId")) || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ] ,
"$or" : [
{ "metricId" : [ 1234 ] } ,
{ "spaceId" : [ 1000 ] }
]
} ,
{ "scope" : [ "Service" ] }
]
}
「$or」は、一部のアプリケーションではすでに通常のキーとして使用されている可能性があります (まれですが)。このような場合、Ruler は下位互換性を維持するために最善を尽くします。上記の 3 つの条件が該当する場合にのみ、ルーラーは動作を変更します。これは、ルールが本当に OR を必要としており、今日まで誤って設定されていたと想定されるためです。たとえば、以下のルールは、ルールとイベントで「$or」を通常のフィールド名として扱い、通常のルールとして機能し続けます。
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : {
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
}
}
「$or」が Ruler によって通常のフィールド名として解析される例については、 /src/test/data/normalRulesWithOrWording.json
を参照してください。
「Or」関係プリミティブとしてのキーワード「$or」は、イベントとルールの両方で通常のフィールドとして設計しないでください。 Ruler は、「$or」が通常のフィールド名として解析される従来のルールをサポートしています。これにより、下位互換性が維持され、チームが従来の「$or」の使用をイベントやルールから通常のファイル名として移行する時間を確保できます。 「$or」プリミティブとしての「$or」と通常のフィールド名としての「$or」の混合使用は、「$or」に関する非常に厄介な曖昧さの発生を避けるために、ルーラーでは意図的にサポートされていません。
ルーラーの使用方法は 2 つあります。複数のルールを「マシン」にコンパイルし、そのrulesForEvent()
メソッドまたはrulesForJSONEvent()
メソッドのいずれかを使用して、どのルールがイベントに一致するかを確認できます。これら 2 つの方法の違いについては、以下で説明します。この説明では、違いが重要な場合を除き、一般的にrulesForEvent()
使用します。
あるいは、単一の静的ブール値メソッドを使用して、個々のイベントが特定のルールに一致するかどうかを判断できます。
単一の静的ブール メソッドRuler.matchesRule(event, rule)
があり、両方の引数が JSON 文字列として提供されます。
注: Ruler.matches(event, rule)
と呼ばれる別の非推奨メソッドがあります。これは、結果がrulesForJSONEvent()
およびrulesForEvent()
と矛盾するため、使用しないでください。詳細については、 Ruler.matches(event, rule)
に関するドキュメントを参照してください。
マッチング時間はルールの数には依存しません。これは、選択したいルールが複数ある場合、特にコンパイルされたマシンを保存する方法がある場合に最適です。
マッチング時間は、ワイルドカード ルールおよびワイルドカード以外のルールによって引き起こされる非決定性の程度に影響されます。理論上の最悪のイベントに一致するワイルドカード ルール プレフィックスの数が増えると、パフォーマンスが低下します。これを回避するには、同じイベント フィールドに関連するワイルドカード ルールで、最初のワイルドカード文字に至るまでの共通のプレフィックスを避ける必要があります。共通の接頭辞が必要な場合は、最小限の数のワイルドカード文字を使用し、ワイルドカード文字の後に出現する文字シーケンスの繰り返しを制限します。 MachineComplexityEvaluator を使用すると、マシンを評価し、非決定性の程度、つまり「複雑さ」(つまり、理論上の最悪のイベントに一致するワイルドカード ルール プレフィックスの数) を判断できます。以下に、複雑さのスコアが増加するとパフォーマンスが低下する一般的なことを示すデータ ポイントをいくつか示します。
アプリケーションを保護するには、マシンの複雑さを制限することが重要です。マシンの複雑さを制限するには、少なくとも 2 つの異なる戦略があります。どちらがより理にかなっているかは、アプリケーションによって異なります。
戦略 #1 は、すべてのルールを含むマシンの実際の複雑さを測定するという点で、より理想的です。可能であれば、この戦略を使用する必要があります。欠点は、一度に 1 つのルールを最大数まで作成できるコントロール プレーンがあるとします。次に、これらのコントロール プレーン操作ごとに、既存のルールをすべてロードして検証を実行する必要があります。これは非常に高価になる可能性があります。また、競合状態が発生しやすくなります。戦略 #2 は妥協です。戦略 #2 で使用されるしきい値は、ルールごとのしきい値であるため、戦略 #1 よりも低くなります。すべてのルールを追加したマシンの複雑さを 300 以下にしたいとします。その後、たとえば戦略 2 を使用すると、各単一ルール マシンの複雑さを 10 に制限し、ワイルドカード パターンを含むルールを 30 個許可できます。 。複雑さが完全に加算的である (可能性は低い) 最悪のケースでは、マシンの複雑さは 300 になります。欠点は、複雑さが完全に加算的である可能性は低いため、ワイルドカードを含むルールの数が増加することです。不必要に制限される可能性があります。
戦略 #2 の場合、ルールの格納方法に応じて、ワイルドカードを含むルールの数を制限するために、どのルールが非決定的である (つまり、ワイルドカード パターンを含む) かを示す追加の属性をルールに追加する必要がある場合があります。
以下は、戦略 2 の場合など、特定のパターンの複雑さを制限する方法を示すコード スニペットです。
public class Validate {
private void validate ( String pattern , MachineComplexityEvaluator machineComplexityEvaluator ) {
// If we cannot compile, then return exception.
List < Map < String , List < Patterns >>> compilationResult = Lists . newArrayList ();
try {
compilationResult . addAll ( JsonRuleCompiler . compile ( pattern ));
} catch ( Exception e ) {
InvalidPatternException internalException =
EXCEPTION_FACTORY . invalidPatternException ( e . getLocalizedMessage ());
throw ExceptionMapper . mapToModeledException ( internalException );
}
// Validate wildcard patterns. Look for wildcard patterns out of all patterns that have been used.
Machine machine = new Machine ();
int i = 0 ;
for ( Map < String , List < Patterns >> rule : compilationResult ) {
if ( containsWildcard ( rule )) {
// Add rule to machine for complexity evaluation.
machine . addPatternRule ( Integer . toString (++ i ), rule );
}
}
// Machine has all rules containing wildcard match types. See if the complexity is under the limit.
int complexity = machine . evaluateComplexity ( machineComplexityEvaluator );
if ( complexity > MAX_MACHINE_COMPLEXITY ) {
InvalidPatternException internalException = EXCEPTION_FACTORY . invalidPatternException ( "Rule is too complex" );
throw ExceptionMapper . mapToModeledException ( internalException );
}
}
private boolean containsWildcard ( Map < String , List < Patterns >> rule ) {
for ( List < Patterns > fieldPatterns : rule . values ()) {
for ( Patterns fieldPattern : fieldPatterns ) {
if ( fieldPattern . type () == WILDCARD || fieldPattern . type () == ANYTHING_BUT_WILDCARD ) {
return true ;
}
}
}
return false ;
}
}
操作するメイン クラスは、ステート マシン ベースのルール マッチングを実装します。興味深い方法は次のとおりです。
addRule()
- 新しいルールをマシンに追加しますdeleteRule()
- マシンからルールを削除しますrulesForEvent()
/ rulesForJSONEvent()
- イベントに一致するマシン内のルールを検索しますMachine
とGenericMachine<T>
の 2 つのフレーバーがあります。 Machine は単にGenericMachine<String>
です。 API はジェネリック型を「名前」として参照します。これは歴史を反映しています。文字列バージョンが最初に構築され、それが保存して返す文字列はルール名と見なされていました。
安全のため、ルールの「名前」に使用される型は不変である必要があります。ルール名として使用されているオブジェクトの内容を変更すると、Ruler の動作が中断される可能性があります。
GenericMachine および Machine コンストラクターは、オプションで、次の構成オプションを公開する GenericMachineConfiguration オブジェクトを受け入れます。
デフォルト: false 通常、指定されたキー サブシーケンスとパターンが以前に追加されている場合、または指定されたキー サブシーケンスにパターンがすでに追加されている場合、NameStates はそのキー サブシーケンスとパターンに対して再利用されます。したがって、デフォルトでは、NameState の再利用は便宜的に行われます。ただし、このフラグを true に設定すると、キーのサブシーケンスに対して NameState の再利用が強制されます。これは、キー サブシーケンスが以前に追加されている場合、キー サブシーケンスに追加される最初のパターンが NameState を再利用することを意味します。つまり、各キー サブシーケンスには単一の NameState があります。これにより、場合によってはメモリ使用率が指数関数的に改善されますが、個々の NameState に保存されるサブルールが増加し、Ruler がそれを反復処理することになり、実行時のパフォーマンスが若干低下する可能性があります。これは、下位互換性のためにデフォルトで false に設定されていますが、最もレイテンシーに敏感なアプリケーションを除くすべてのアプリケーションは、これを true に設定することで恩恵を受ける可能性があります。
以下に簡単な例を示します。考慮する:
machine . addRule ( "0" , "{"key1": ["a", "b", "c"]}" ) ;
パターン「a」は NameState を作成し、additionNameStateReuse=false であっても、2 番目のパターン (「b」) と 3 番目のパターン (「c」) は同じ NameState を再利用します。ただし、代わりに次のことを考慮してください。
machine . addRule ( "0" , "{"key1": ["a"]}" ) ;
machine . addRule ( "1" , "{"key1": ["b"]}" ) ;
machine . addRule ( "2" , "{"key1": ["c"]}" ) ;
ここで、AdditionalNameStateReuse=false を指定すると、最終的に 3 つの NameState が作成されます。これは、ルールを追加するたびにキー サブシーケンスで最初に見つかったパターンによって新しい NameState が作成されるためです。したがって、「a」、「b」、および「c」はすべて独自の NameState を取得します。ただし、AdditionalNameStateReuse=true の場合、「a」は新しい NameState を作成し、「b」と「c」はこの同じ NameState を再利用します。これは、キー サブシーケンス「key1」の NameState が既に存在することを保存することによって実現されます。
各 addRule が異なるルール名を使用するか、同じルール名を使用するかは問題ではないことに注意してください。
このメソッドのすべての形式には同じ最初の引数があり、これはルールの名前を提供し、 rulesForEvent()
によって返される String です。残りの引数は、名前と値のペアを提供します。これらは、上記の例のように (String、Reader、InputStream、またはbyte[]
経由) JSON で提供することも、 Map<String, List<String>>
として提供することもできます。ここで、キーはフィールド名と値は一致する可能性のあるリストです。上の例を使用すると、 detail.state
という名前のキーがあり、その値は"initializing"
と"running"
を含むリストになります。
注: このメソッド (およびdeleteRule()
) は同期されるため、いつでも 1 つのスレッドだけがマシンを更新できます。