├── README.md ├── templates └── zaws_zabbix_template.xml └── zaws.go /README.md: -------------------------------------------------------------------------------- 1 | # About "ZAWS" 2 | 3 | Zabbix AWS monitoring template 4 | This template is supported over Zabbix 3.0. 5 | 6 | # Features 7 | 8 | * EC2 monitoring 9 | * Automatically registration EC2 instances information by using Zabbix LLD host prototype. 10 | * Automatically registration EC2 CloudWatch metrics information by using Zabbix LLD item prototype. 11 | * ELB monitoring 12 | * Automatically registration ELB instances information by using Zabbix LLD host prototype. 13 | * Automatically registration ELB CloudWatch metrics information by using Zabbix LLD item prototype. 14 | 15 | # Requirements 16 | 17 | * Zabbix >= 3.0 18 | 19 | # Installation & Setting 20 | 21 | ## 1. Download 22 | 23 | Download zaws command line tool and Zabbix template xml file. 24 | 25 | Please get the binary file that is appropriate for your environment architecture. 26 | 27 | [binary file url](https://github.com/ike-dai/zaws/releases) 28 | 29 | [template xml file url](https://github.com/ike-dai/zaws/tree/master/templates/zaws_zabbix_template.xml) 30 | 31 | ## 2. Copy to Externalscripts directory 32 | 33 | Please copy command line tool file to your zabbix servers externalscripts directory. 34 | 35 | for example: 36 | 37 | $ cp zaws-linux-amd64 /usr/lib/zabbix/externalscripts/zaws 38 | 39 | ## 3. Import zabbix template xml file 40 | 41 | [Configuration]->[Templates]->[Import] 42 | 43 | Please import "zaws_zabbix_template.xml" 44 | 45 | ## 4. Register host 46 | 47 | [Configuration]->[Hosts]->[Create host] 48 | 49 | * Host name: any 50 | * Groups: any 51 | * Agent interfaces: any (not used in this tool) 52 | * Templates: Template AWS 53 | * Macros: please set 3 macro 54 | * {$REGION}: Please set AWS region name (e.g. ap-northeast-1) 55 | * {$KEY}: Please set AWS ACCESS KEY ID (e.g. AKI........) 56 | * {$SECRET}: Please set AWS SECRET ACCESS KEY 57 | 58 | # Contact 59 | 60 | Please send feedback to me. 61 | 62 | Daisuke IKEDA 63 | 64 | Twitter: [@ike_dai](https://twitter.com/ike_dai) 65 | 66 | e-mail: 67 | 68 | # License 69 | 70 | Licensed under the Apache License, Version 2.0. The Apache v2 full text is published at this [link](http://www.apache.org/licenses/LICENSE-2.0). 71 | 72 | Copyright 2016 Daisuke IKEDA. 73 | -------------------------------------------------------------------------------- /templates/zaws_zabbix_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3.0 4 | 2016-02-09T23:40:17Z 5 | 6 | 7 | AWS EC2 8 | 9 | 10 | AWS ELB 11 | 12 | 13 | Discovered hosts 14 | 15 | 16 | Templates 17 | 18 | 19 | 20 | 163 | 309 | 455 | 456 | 457 | -------------------------------------------------------------------------------- /zaws.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "github.com/AlekSi/zabbix-sender" 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/aws/credentials" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/aws/aws-sdk-go/service/cloudwatch" 12 | "github.com/aws/aws-sdk-go/service/ec2" 13 | "github.com/aws/aws-sdk-go/service/elb" 14 | "net" 15 | "os" 16 | "strconv" 17 | "time" 18 | ) 19 | 20 | type Zaws struct { 21 | Region string 22 | AccessKeyId string 23 | SecretKeyId string 24 | TargetId string 25 | MetricName string 26 | ZabbixHost string 27 | ZabbixPort string 28 | AwsSession *session.Session 29 | } 30 | 31 | func NewZaws() *Zaws { 32 | zaws := new(Zaws) 33 | zaws.SetOption() 34 | zaws.AwsSession = session.New(&aws.Config{ 35 | Region: aws.String(zaws.Region), 36 | Credentials: credentials.NewStaticCredentials(zaws.AccessKeyId, zaws.SecretKeyId, ""), 37 | }) 38 | return zaws 39 | } 40 | 41 | func (z *Zaws) SetOption() { 42 | flag.StringVar(&z.Region, "region", "ap-northeast-1", "Set AWS region") 43 | flag.StringVar(&z.Region, "r", "ap-northeast-1", "Set AWS region") 44 | flag.StringVar(&z.AccessKeyId, "key", os.Getenv("AWS_ACCESS_KEY_ID"), "Set AWS API Access key id") 45 | flag.StringVar(&z.AccessKeyId, "k", os.Getenv("AWS_ACCESS_KEY_ID"), "Set AWS API Access key id") 46 | flag.StringVar(&z.SecretKeyId, "secret", os.Getenv("AWS_SECRET_ACCESS_KEY"), "Set AWS API Secret key id") 47 | flag.StringVar(&z.SecretKeyId, "s", os.Getenv("AWS_SECRET_ACCESS_KEY"), "Set AWS API Secret key id") 48 | flag.StringVar(&z.TargetId, "id", "", "Set target object id") 49 | flag.StringVar(&z.TargetId, "i", "", "Set target object id") 50 | flag.StringVar(&z.MetricName, "metric", "", "Set metric name") 51 | flag.StringVar(&z.MetricName, "m", "", "Set metric name") 52 | flag.StringVar(&z.ZabbixHost, "host", "localhost", "Set zabbix host name") 53 | flag.StringVar(&z.ZabbixHost, "h", "localhost", "Set zabbix host name") 54 | flag.StringVar(&z.ZabbixPort, "port", "10051", "Set zabbix host port") 55 | flag.StringVar(&z.ZabbixPort, "p", "10051", "Set zabbix host port") 56 | flag.Parse() 57 | if z.AccessKeyId == "" || z.SecretKeyId == "" { 58 | fmt.Println("[ERROR]: Please set key information") 59 | usage() 60 | } 61 | } 62 | 63 | // Declare Struct 64 | type LldJson struct { 65 | Data []Data `json:"data"` 66 | } 67 | 68 | type Data struct { 69 | MetricName string `json:"{#METRIC.NAME},omitempty"` 70 | MetricUnit string `json:"{#METRIC.UNIT},omitempty"` 71 | MetricNamespace string `json:"{#METRIC.NAMESPACE},omitempty"` 72 | InstanceName string `json:"{#INSTANCE.NAME},omitempty"` 73 | InstanceType string `json:"{#INSTANCE.TYPE},omitempty"` 74 | InstanceId string `json:"{#INSTANCE.ID},omitempty"` 75 | InstancePrivateAddr string `json:"{#INSTANCE.PRIVATE.ADDR},omitempty"` 76 | ElbName string `json:"{#ELB.NAME},omitempty"` 77 | ElbDnsName string `json:"{#ELB.DNS.NAME},omitempty"` 78 | } 79 | 80 | // Common util 81 | 82 | func usage() { 83 | fmt.Println("Usage: zaws service method [target] [-region|-r] [-key|-k] [-secret|-s] [-id|-i] [-metric|-m] [-host|h] [-port|p]") 84 | os.Exit(1) 85 | } 86 | 87 | func convert_to_lldjson_string(data []Data) string { 88 | lld_json := LldJson{data} 89 | convert_json, _ := json.Marshal(lld_json) 90 | return string(convert_json) 91 | } 92 | 93 | // Access AWS API 94 | func get_metric_list(sess *session.Session, identity_name, target_id string) []*cloudwatch.Metric { 95 | svc := cloudwatch.New(sess) 96 | params := &cloudwatch.ListMetricsInput{ 97 | Dimensions: []*cloudwatch.DimensionFilter{ 98 | { 99 | Name: aws.String(identity_name), 100 | Value: aws.String(target_id), 101 | }, 102 | }, 103 | } 104 | resp, err := svc.ListMetrics(params) 105 | if err != nil { 106 | fmt.Printf("[ERROR] Fail ListMetrics API call: %s \n", err.Error()) 107 | return nil 108 | } 109 | return resp.Metrics 110 | } 111 | func get_metric_statistics(metric_name, metric_namespace string) *string { 112 | sum_metric_list := []string{ 113 | "RequestCount", 114 | "HTTPCode_Backend_2XX", 115 | "HTTPCode_Backend_3XX", 116 | "HTTPCode_Backend_4XX", 117 | "HTTPCode_Backend_5XX", 118 | "HTTPCode_ELB_4XX", 119 | "HTTPCode_ELB_5XX", 120 | "HTTPCode_ELB_5XX", 121 | } 122 | if metric_namespace == "AWS/ELB" { 123 | for _, value := range sum_metric_list { 124 | if value == metric_name { 125 | return aws.String("Sum") 126 | } 127 | } 128 | } 129 | return aws.String("Average") 130 | } 131 | 132 | func get_metric_stats(sess *session.Session, identity_name, target_id, metric_name, metric_namespace string) []*cloudwatch.Datapoint { 133 | 134 | svc := cloudwatch.New(sess) 135 | t := time.Now() 136 | input := &cloudwatch.GetMetricStatisticsInput{ 137 | Namespace: aws.String(metric_namespace), 138 | Statistics: []*string{get_metric_statistics(metric_name, metric_namespace)}, 139 | EndTime: aws.Time(t), 140 | Period: aws.Int64(300), 141 | StartTime: aws.Time(t.Add(time.Duration(-10) * time.Minute)), 142 | MetricName: aws.String(metric_name), 143 | Dimensions: []*cloudwatch.Dimension{ 144 | { 145 | Name: aws.String(identity_name), 146 | Value: aws.String(target_id), 147 | }, 148 | }, 149 | } 150 | value, err := svc.GetMetricStatistics(input) 151 | if err != nil { 152 | fmt.Printf("[ERROR] Fail GetMetricStatistics API call: %s \n", err.Error()) 153 | return nil 154 | } 155 | return value.Datapoints 156 | } 157 | 158 | func get_ec2_list(sess *session.Session) []*ec2.Instance { 159 | var instances []*ec2.Instance 160 | svc := ec2.New(sess) 161 | resp, err := svc.DescribeInstances(nil) 162 | 163 | if err != nil { 164 | fmt.Printf("[ERROR] Fail DescribeInstances API call: %s \n", err.Error()) 165 | os.Exit(1) 166 | } 167 | for _, reservation := range resp.Reservations { 168 | instances = append(instances, reservation.Instances...) 169 | } 170 | return instances 171 | } 172 | 173 | func get_elb_list(sess *session.Session) []*elb.LoadBalancerDescription { 174 | svc := elb.New(sess) 175 | params := &elb.DescribeLoadBalancersInput{ 176 | LoadBalancerNames: []*string{}, 177 | } 178 | resp, err := svc.DescribeLoadBalancers(params) 179 | 180 | if err != nil { 181 | fmt.Printf("[ERROR] Fail DescribeLoadBalancers API call: %s \n", err.Error()) 182 | return nil 183 | } 184 | return resp.LoadBalancerDescriptions 185 | } 186 | 187 | // zaws method 188 | func (z *Zaws) ShowEc2List() { 189 | list := make([]Data, 0) 190 | instances := get_ec2_list(z.AwsSession) 191 | for _, instance := range instances { 192 | data := Data{InstanceType: *instance.InstanceType, InstanceId: *instance.InstanceId} 193 | if instance.PrivateIpAddress != nil { 194 | data.InstancePrivateAddr = *instance.PrivateIpAddress 195 | } 196 | for _, tag := range instance.Tags { 197 | if *tag.Key == "Name" { 198 | data.InstanceName = *tag.Value 199 | } 200 | } 201 | if data.InstanceName == "" { 202 | data.InstanceName = *instance.InstanceId 203 | } 204 | list = append(list, data) 205 | } 206 | fmt.Printf(convert_to_lldjson_string(list)) 207 | } 208 | 209 | func (z *Zaws) ShowElbList() { 210 | list := make([]Data, 0) 211 | elbs := get_elb_list(z.AwsSession) 212 | for _, elb := range elbs { 213 | data := Data{ElbName: *elb.LoadBalancerName, ElbDnsName: *elb.DNSName} 214 | list = append(list, data) 215 | } 216 | fmt.Printf(convert_to_lldjson_string(list)) 217 | } 218 | 219 | func (z *Zaws) ShowEC2CloudwatchMetricsList() { 220 | list := make([]Data, 0) 221 | metrics := get_metric_list(z.AwsSession, "InstanceId", z.TargetId) 222 | for _, metric := range metrics { 223 | datapoints := get_metric_stats(z.AwsSession, "InstanceId", z.TargetId, *metric.MetricName, *metric.Namespace) 224 | data := Data{MetricName: *metric.MetricName, MetricNamespace: *metric.Namespace} 225 | if len(datapoints) > 0 { 226 | data.MetricUnit = *datapoints[0].Unit 227 | } 228 | list = append(list, data) 229 | } 230 | 231 | fmt.Printf(convert_to_lldjson_string(list)) 232 | } 233 | 234 | func (z *Zaws) ShowELBCloudwatchMetricsList() { 235 | list := make([]Data, 0) 236 | metrics := get_metric_list(z.AwsSession, "LoadBalancerName", z.TargetId) 237 | for _, metric := range metrics { 238 | datapoints := get_metric_stats(z.AwsSession, "LoadBalancerName", z.TargetId, *metric.MetricName, *metric.Namespace) 239 | metric_name := *metric.MetricName 240 | for _, dimension := range metric.Dimensions { 241 | if *dimension.Name == "AvailabilityZone" { 242 | metric_name = *metric.MetricName + "." + *dimension.Value 243 | break 244 | } 245 | } 246 | data := Data{MetricName: metric_name, MetricNamespace: *metric.Namespace} 247 | if len(datapoints) > 0 { 248 | data.MetricUnit = *datapoints[0].Unit 249 | } 250 | list = append(list, data) 251 | } 252 | 253 | fmt.Printf(convert_to_lldjson_string(list)) 254 | } 255 | 256 | func (z *Zaws) SendEc2MetricStats() { 257 | z.SendMetricStats("InstanceId") 258 | } 259 | func (z *Zaws) SendElbMetricStats() { 260 | z.SendMetricStats("LoadBalancerName") 261 | } 262 | 263 | func (z *Zaws) SendMetricStats(identity_name string) { 264 | var send_data []zabbix_sender.DataItem 265 | 266 | metrics := get_metric_list(z.AwsSession, identity_name, z.TargetId) 267 | for _, metric := range metrics { 268 | datapoints := get_metric_stats(z.AwsSession, identity_name, z.TargetId, *metric.MetricName, *metric.Namespace) 269 | metric_name := *metric.MetricName 270 | for _, dimension := range metric.Dimensions { 271 | if *dimension.Name == "AvailabilityZone" { 272 | metric_name = *metric.MetricName + "." + *dimension.Value 273 | break 274 | } 275 | } 276 | 277 | if len(datapoints) > 0 { 278 | data_time := *datapoints[0].Timestamp 279 | var val float64 280 | if datapoints[0].Average == (*float64)(nil) { 281 | val = *datapoints[0].Sum 282 | } else { 283 | val = *datapoints[0].Average 284 | } 285 | send_data = append(send_data, zabbix_sender.DataItem{Hostname: z.TargetId, Key: "cloudwatch.metric[" + metric_name + "]", Value: strconv.FormatFloat(val, 'f', 4, 64), Timestamp: data_time.Unix()}) 286 | } 287 | } 288 | addr, _ := net.ResolveTCPAddr("tcp", z.ZabbixHost+":"+z.ZabbixPort) 289 | res, err := zabbix_sender.Send(addr, send_data) 290 | if err != nil { 291 | fmt.Printf("[ERROR]: zabbix sender error!: %s", err) 292 | os.Exit(1) 293 | } 294 | fmt.Printf("[INFO]: Successful sending data to Zabbix: resp", res) 295 | } 296 | 297 | func main() { 298 | if len(os.Args) < 3 { 299 | usage() 300 | } 301 | switch os.Args[1] { 302 | case "ec2": 303 | switch os.Args[2] { 304 | case "list": 305 | os.Args = os.Args[2:] 306 | zaws := NewZaws() 307 | zaws.ShowEc2List() 308 | default: 309 | usage() 310 | } 311 | case "elb": 312 | switch os.Args[2] { 313 | case "list": 314 | os.Args = os.Args[2:] 315 | zaws := NewZaws() 316 | zaws.ShowElbList() 317 | default: 318 | usage() 319 | } 320 | case "cloudwatch": 321 | switch os.Args[2] { 322 | case "list": 323 | if len(os.Args) < 4 { 324 | usage() 325 | } 326 | switch os.Args[3] { 327 | case "ec2": 328 | os.Args = os.Args[3:] 329 | zaws := NewZaws() 330 | zaws.ShowEC2CloudwatchMetricsList() 331 | case "rds": 332 | case "elb": 333 | os.Args = os.Args[3:] 334 | zaws := NewZaws() 335 | zaws.ShowELBCloudwatchMetricsList() 336 | default: 337 | usage() 338 | } 339 | case "stats": 340 | if len(os.Args) < 4 { 341 | usage() 342 | } 343 | switch os.Args[3] { 344 | case "ec2": 345 | os.Args = os.Args[3:] 346 | zaws := NewZaws() 347 | zaws.SendEc2MetricStats() 348 | case "elb": 349 | os.Args = os.Args[3:] 350 | zaws := NewZaws() 351 | zaws.SendElbMetricStats() 352 | default: 353 | usage() 354 | } 355 | default: 356 | usage() 357 | } 358 | 359 | default: 360 | usage() 361 | } 362 | os.Exit(0) 363 | } 364 | --------------------------------------------------------------------------------