terrafying-components 1.9.4 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ef900e4cc15b61abf78396071e7feffb6c834968d005eddacb23432289e571a
4
- data.tar.gz: 2461a717bfdb0b7b173234c4001b088b676f2884f5f27fb01900e856aa741d61
3
+ metadata.gz: 0e0f9a35d9a157e53bd64710ea50fdf04683b4f89889db941b5986dc69bf99bb
4
+ data.tar.gz: 1c5a884ed70308bdcc23ad34e26618be3d09a58e80586724247865e5e7fefbd2
5
5
  SHA512:
6
- metadata.gz: 5d5cc4bbeb7e72852cf93405db825530c6726adf7ffeda1c84b3de9bf01d13dbd63a423604ab6bee3fd3a7364eb80329ab4b441c80e6fcb8f03526d54cc30c04
7
- data.tar.gz: 5a7d14501508e4ff6c430faf690ea7f0b87495d17b54490e0cd62613d7251bf5d069719055d2bac3d9c50488bf5d1d77e475ae627e25fadd9bf37e9372a98b54
6
+ metadata.gz: f73618b804791e70061c0d78688d05efffeebdc775674d6d26aec351746a08acea0cfa2af315f5b7ca766c6514f4ba4944beb5314397020bb22b8584d194193c
7
+ data.tar.gz: c8cc39bba9ec39f1b784de28450b26d533520f2baf21d760358333a63f19bb76e80d6a36e66c63e93c6acd1cfa568601c54a629897c0e2ef3c5d889e9599227d
@@ -3,6 +3,7 @@ require 'terrafying/components/endpoint'
3
3
  require 'terrafying/components/endpointservice'
4
4
  require 'terrafying/components/selfsignedca'
5
5
  require 'terrafying/components/letsencrypt'
6
+ require 'terrafying/components/prometheus'
6
7
  require 'terrafying/components/service'
7
8
  require 'terrafying/components/subnet'
8
9
  require 'terrafying/components/vpc'
@@ -153,7 +153,6 @@ module Terrafying
153
153
  resource :aws_autoscaling_policy, policy_name, {
154
154
  name: policy_name,
155
155
  autoscaling_group_name: @asg,
156
- adjustment_type: 'ChangeInCapacity',
157
156
  policy_type: 'TargetTrackingScaling',
158
157
  target_tracking_configuration: {
159
158
  predefined_metric_specification: {
@@ -119,12 +119,7 @@ module Terrafying
119
119
  @ports.each { |port|
120
120
  port_ident = "#{ident}-#{port[:downstream_port]}"
121
121
 
122
- target_group = resource :aws_lb_target_group, port_ident, {
123
- name: port_ident,
124
- port: port[:downstream_port],
125
- protocol: port[:type].upcase,
126
- vpc_id: vpc.id,
127
- }.merge(port.has_key?(:health_check) ? { health_check: port[:health_check] }: {})
122
+ default_action = port.key?(:action) ? port[:action] : forward_to_tg(port, port_ident, vpc)
128
123
 
129
124
  ssl_options = alb_certs(port, port_ident)
130
125
 
@@ -132,16 +127,10 @@ module Terrafying
132
127
  load_balancer_arn: @id,
133
128
  port: port[:upstream_port],
134
129
  protocol: port[:type].upcase,
135
- default_action: {
136
- target_group_arn: target_group,
137
- type: "forward",
138
- },
130
+ default_action: default_action,
139
131
  }.merge(ssl_options)
140
132
 
141
- @targets << Struct::Target.new(
142
- target_group: target_group,
143
- listener: listener
144
- )
133
+ register_target(default_action[:target_group_arn], listener) if default_action[:type] == 'forward'
145
134
  }
146
135
 
147
136
  @alias_config = {
@@ -149,10 +138,30 @@ module Terrafying
149
138
  zone_id: output_of(:aws_lb, ident, :zone_id),
150
139
  evaluate_target_health: true,
151
140
  }
152
-
153
141
  self
154
142
  end
155
143
 
144
+ def forward_to_tg(port, port_ident, vpc)
145
+ target_group = resource :aws_lb_target_group, port_ident, {
146
+ name: port_ident,
147
+ port: port[:downstream_port],
148
+ protocol: port[:type].upcase,
149
+ vpc_id: vpc.id,
150
+ }.merge(port.key?(:health_check) ? { health_check: port[:health_check] }: {})
151
+
152
+ {
153
+ type: 'forward',
154
+ target_group_arn: target_group
155
+ }
156
+ end
157
+
158
+ def register_target(target_group, listener)
159
+ @targets << Struct::Target.new(
160
+ target_group: target_group,
161
+ listener: listener
162
+ )
163
+ end
164
+
156
165
  def alb_certs(port, port_ident)
157
166
  return {} unless port.key? :ssl_certificate
158
167
 
@@ -7,25 +7,57 @@ PORT_NAMES = {
7
7
  }
8
8
 
9
9
  def enrich_ports(ports)
10
- ports.map { |port|
10
+ ports = add_upstream_downstream(ports)
11
+ ports = add_redirects(ports)
12
+ add_names(ports)
13
+ end
14
+
15
+ def add_upstream_downstream(ports)
16
+ ports.map do |port|
11
17
  if port.is_a?(Numeric)
12
18
  port = { upstream_port: port, downstream_port: port }
13
19
  end
14
20
 
15
- if port.has_key?(:number)
21
+ if port.key?(:number)
16
22
  port[:upstream_port] = port[:number]
17
23
  port[:downstream_port] = port[:number]
18
24
  end
25
+ port
26
+ end
27
+ end
19
28
 
20
- port = {
21
- type: "tcp",
22
- name: PORT_NAMES.fetch(port[:upstream_port], port[:upstream_port].to_s),
23
- }.merge(port)
24
-
29
+ def add_redirects(ports)
30
+ ports.flat_map do |port|
31
+ if port.key? :redirect_http_from_port
32
+ redirect_port = redirect_http(port[:redirect_http_from_port], port[:upstream_port])
33
+ port.delete(:redirect_http_from_port)
34
+ return [port, redirect_port]
35
+ end
25
36
  port
37
+ end
38
+ end
39
+
40
+ def redirect_http(from_port, to_port)
41
+ {
42
+ upstream_port: from_port,
43
+ downstream_port: from_port,
44
+ type: 'http',
45
+ action: {
46
+ type: 'redirect',
47
+ redirect: { port: to_port, protocol: 'HTTPS', status_code: 'HTTP_301' }
48
+ }
26
49
  }
27
50
  end
28
51
 
52
+ def add_names(ports)
53
+ ports.map do |port|
54
+ {
55
+ type: 'tcp',
56
+ name: PORT_NAMES.fetch(port[:upstream_port], port[:upstream_port].to_s),
57
+ }.merge(port)
58
+ end
59
+ end
60
+
29
61
  def from_port(port)
30
62
  return port unless port_range?(port)
31
63
  port.split('-').first.to_i
@@ -0,0 +1,329 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'terrafying'
5
+ require 'terrafying/components'
6
+
7
+ module Terrafying
8
+ module Components
9
+ class Prometheus < Terrafying::Context
10
+ attr_reader :prometheus
11
+
12
+ def self.create_in(options)
13
+ new(**options).tap(&:create)
14
+ end
15
+
16
+ def self.find_in(vpc)
17
+ new(vpc: vpc).find
18
+ end
19
+
20
+ def initialize(
21
+ vpc:,
22
+ thanos_name: 'thanos',
23
+ thanos_version: 'master-2018-10-29-8f247d6',
24
+ prom_name: 'prometheus',
25
+ prom_version: 'v2.4.3'
26
+ )
27
+ super()
28
+ @vpc = vpc
29
+ @thanos_name = thanos_name
30
+ @thanos_version = thanos_version
31
+ @prom_name = prom_name
32
+ @prom_version = prom_version
33
+ end
34
+
35
+ def find
36
+ @security_group = aws.security_groups_in_vpc(
37
+ @vpc.id,
38
+ "dynamicset-#{@vpc.name}-#{@prom_name}"
39
+ )
40
+ end
41
+
42
+ def create
43
+ thanos_peers = @vpc.zone.qualify(@thanos_name)
44
+
45
+ @thanos = create_thanos(thanos_peers)
46
+ create_thanos_cloudwatch_alert(@thanos)
47
+
48
+ @prometheus = create_prom(thanos_peers)
49
+ @security_group = @prometheus.egress_security_group
50
+ create_prometheus_cloudwatch_alert(@prometheus)
51
+ allow_thanos_gossip(@prometheus.egress_security_group)
52
+
53
+ @prometheus.used_by_cidr(@vpc.cidr)
54
+ @thanos.used_by_cidr(@vpc.cidr)
55
+ end
56
+
57
+ def create_prom(thanos_peers)
58
+ add! Terrafying::Components::Service.create_in(
59
+ @vpc, @prom_name,
60
+ ports: [
61
+ {
62
+ type: 'http',
63
+ number: 9090,
64
+ health_check: { path: '/status', protocol: 'HTTP' }
65
+ }
66
+ ],
67
+ instance_type: 'm5.large',
68
+ iam_policy_statements: thanos_store_access,
69
+ instances: { max: 3, min: 1, desired: 2 },
70
+ units: [prometheus_unit, thanos_sidecar_unit(thanos_peers)],
71
+ files: [prometheus_conf, thanos_bucket]
72
+ )
73
+ end
74
+
75
+ def allow_thanos_gossip(security_group)
76
+ rule_ident = Digest::SHA2.hexdigest("#{security_group}-thanos-#{@vpc.name}")[0..24]
77
+ resource :aws_security_group_rule, rule_ident, {
78
+ security_group_id: security_group,
79
+ type: 'ingress',
80
+ from_port: 10900,
81
+ to_port: 10902,
82
+ protocol: 'tcp',
83
+ cidr_blocks: [@vpc.cidr]
84
+ }
85
+ end
86
+
87
+ def create_thanos(thanos_peers)
88
+ add! Terrafying::Components::Service.create_in(
89
+ @vpc, @thanos_name,
90
+ ports: [
91
+ {
92
+ number: 10902,
93
+ health_check: {
94
+ path: '/status',
95
+ protocol: 'HTTP'
96
+ }
97
+ },
98
+ {
99
+ number: 10901
100
+ },
101
+ {
102
+ number: 10900
103
+ }
104
+ ],
105
+ instance_type: 't3.medium',
106
+ units: [thanos_unit(thanos_peers)],
107
+ instances: { max: 3, min: 1, desired: 2 }
108
+ )
109
+ end
110
+
111
+ def prometheus_unit
112
+ {
113
+ name: 'prometheus.service',
114
+ contents: <<~PROM_UNIT
115
+ [Install]
116
+ WantedBy=multi-user.target
117
+ [Unit]
118
+ Description=Prometheus Service
119
+ After=docker.service
120
+ Requires=docker.service
121
+ [Service]
122
+ ExecStartPre=-/usr/bin/docker network create --driver bridge prom
123
+ ExecStartPre=-/usr/bin/docker kill prometheus
124
+ ExecStartPre=-/usr/bin/docker rm prometheus
125
+ ExecStartPre=/usr/bin/docker pull quay.io/prometheus/prometheus:#{@prom_version}
126
+ ExecStartPre=-/usr/bin/sed -i "s/{{HOST}}/%H/" /opt/prometheus/prometheus.yml
127
+ ExecStartPre=/usr/bin/install -d -o nobody -g nobody -m 0755 /opt/prometheus/data
128
+ ExecStart=/usr/bin/docker run --name prometheus \
129
+ -p 9090:9090 \
130
+ --network=prom \
131
+ -v /opt/prometheus:/opt/prometheus \
132
+ quay.io/prometheus/prometheus:#{@prom_version} \
133
+ --storage.tsdb.path=/opt/prometheus/data \
134
+ --storage.tsdb.retention=1d \
135
+ --storage.tsdb.min-block-duration=2h \
136
+ --storage.tsdb.max-block-duration=2h \
137
+ --config.file=/opt/prometheus/prometheus.yml \
138
+ --web.enable-lifecycle \
139
+ --log.level=warn
140
+ Restart=always
141
+ RestartSec=30
142
+ PROM_UNIT
143
+ }
144
+ end
145
+
146
+ def thanos_sidecar_unit(thanos_peers)
147
+ {
148
+ name: 'thanos.service',
149
+ contents: <<~THANOS_SIDE
150
+ [Install]
151
+ WantedBy=multi-user.target
152
+ [Unit]
153
+ Description=Thanos Service
154
+ After=docker.service prometheus.service
155
+ Requires=docker.service prometheus.service
156
+ [Service]
157
+ EnvironmentFile=/run/metadata/coreos
158
+ ExecStartPre=-/usr/bin/docker kill thanos
159
+ ExecStartPre=-/usr/bin/docker rm thanos
160
+ ExecStartPre=/usr/bin/docker pull improbable/thanos:#{@thanos_version}
161
+ ExecStart=/usr/bin/docker run --name thanos \
162
+ -p 10900-10902:10900-10902 \
163
+ -v /opt/prometheus:/opt/prometheus \
164
+ -v /opt/thanos:/opt/thanos \
165
+ --network=prom \
166
+ improbable/thanos:#{@thanos_version} \
167
+ sidecar \
168
+ --cluster.peers=#{thanos_peers}:10900 \
169
+ --cluster.advertise-address=$${COREOS_EC2_IPV4_LOCAL}:10900 \
170
+ --grpc-advertise-address=$${COREOS_EC2_IPV4_LOCAL}:10901 \
171
+ --prometheus.url=http://prometheus:9090 \
172
+ --tsdb.path=/opt/prometheus/data \
173
+ --objstore.config-file=/opt/thanos/bucket.yml \
174
+ --log.level=warn
175
+ Restart=always
176
+ RestartSec=30
177
+ THANOS_SIDE
178
+ }
179
+ end
180
+
181
+ def prometheus_conf
182
+ {
183
+ path: '/opt/prometheus/prometheus.yml',
184
+ mode: 0o644,
185
+ contents: <<~PROM
186
+ global:
187
+ external_labels:
188
+ monitor: prometheus
189
+ cluster: "#{@vpc.name}"
190
+ replica: {{HOST}}
191
+ scrape_interval: 15s
192
+ scrape_configs:
193
+ - job_name: "ec2"
194
+ params:
195
+ format: ["prometheus"]
196
+ ec2_sd_configs:
197
+ - region: eu-west-1
198
+ filters:
199
+ - name: vpc-id
200
+ values: ["#{@vpc.id}"]
201
+ - name: tag-key
202
+ values: ["prometheus_port"]
203
+ relabel_configs:
204
+ - source_labels: [__meta_ec2_private_ip, __meta_ec2_tag_prometheus_port]
205
+ replacement: $1:$2
206
+ regex: ([^:]+)(?::\\\\d+)?;(\\\\d+)
207
+ target_label: __address__
208
+ - source_labels: [__meta_ec2_instance_id]
209
+ target_label: instance_id
210
+ - source_labels: [__meta_ec2_tag_envoy_cluster]
211
+ target_label: envoy_cluster
212
+ - source_labels: [__meta_ec2_tag_prometheus_path]
213
+ regex: (.+)
214
+ replacement: $1
215
+ target_label: __metrics_path__
216
+ PROM
217
+ }
218
+ end
219
+
220
+ def thanos_unit(thanos_peers)
221
+ {
222
+ name: 'thanos.service',
223
+ contents: <<~THANOS_UNIT
224
+ [Install]
225
+ WantedBy=multi-user.target
226
+ [Unit]
227
+ Description=Thanos Service
228
+ After=docker.service
229
+ Requires=docker.service
230
+ [Service]
231
+ EnvironmentFile=/run/metadata/coreos
232
+ ExecStartPre=-/usr/bin/docker kill thanos
233
+ ExecStartPre=-/usr/bin/docker rm thanos
234
+ ExecStartPre=/usr/bin/docker pull improbable/thanos:#{@thanos_version}
235
+ ExecStart=/usr/bin/docker run --name thanos \
236
+ -p 10900-10902:10900-10902 \
237
+ improbable/thanos:#{@thanos_version} \
238
+ query \
239
+ --cluster.peers=#{thanos_peers}:10900 \
240
+ --cluster.advertise-address=$${COREOS_EC2_IPV4_LOCAL}:10900 \
241
+ --grpc-advertise-address=$${COREOS_EC2_IPV4_LOCAL}:10901 \
242
+ --query.replica-label=replica \
243
+ --log.level=warn
244
+ Restart=always
245
+ RestartSec=30
246
+ THANOS_UNIT
247
+ }
248
+ end
249
+
250
+ def thanos_bucket
251
+ {
252
+ path: '/opt/thanos/bucket.yml',
253
+ mode: 0o644,
254
+ contents: <<~S3CONF
255
+ type: S3
256
+ config:
257
+ bucket: uswitch-thanos-store
258
+ endpoint: s3.eu-west-1.amazonaws.com
259
+ S3CONF
260
+ }
261
+ end
262
+
263
+ def thanos_store_access
264
+ [
265
+ {
266
+ Action: ['ec2:DescribeInstances'],
267
+ Effect: 'Allow',
268
+ Resource: '*'
269
+ },
270
+ {
271
+ Action: [
272
+ 's3:ListBucket',
273
+ 's3:GetObject',
274
+ 's3:DeleteObject',
275
+ 's3:PutObject'
276
+ ],
277
+ Effect: 'Allow',
278
+ Resource: [
279
+ 'arn:aws:s3:::uswitch-thanos-store/*',
280
+ 'arn:aws:s3:::uswitch-thanos-store'
281
+ ]
282
+ }
283
+ ]
284
+ end
285
+
286
+ def expose_in(vpc)
287
+ @endpoint_service ||= @thanos.with_endpoint_service(acceptance_required: false)
288
+
289
+ options = {}
290
+ endpoint = add! @endpoint_service.expose_in(vpc, options)
291
+ endpoint.used_by_cidr(vpc.cidr)
292
+
293
+ endpoint
294
+ end
295
+
296
+ def cloudwatch_alarm(name, namespace, dimensions)
297
+ resource 'aws_cloudwatch_metric_alarm', name, {
298
+ alarm_name: name,
299
+ comparison_operator: 'GreaterThanOrEqualToThreshold',
300
+ evaluation_periods: '1',
301
+ metric_name: 'UnHealthyHostCount',
302
+ namespace: namespace,
303
+ period: '180',
304
+ threshold: '1',
305
+ statistic: 'Minimum',
306
+ alarm_description: "Monitoring #{name} target group host health",
307
+ dimensions: dimensions,
308
+ alarm_actions: ['arn:aws:sns:eu-west-1:136393635417:prometheus_cloudwatch_topic']
309
+ }
310
+ end
311
+
312
+ def create_prometheus_cloudwatch_alert(service)
313
+ cloudwatch_alarm service.name, 'AWS/ApplicationELB', {
314
+ LoadBalancer: output_of('aws_lb', service.load_balancer.name, 'arn_suffix'),
315
+ TargetGroup: service.load_balancer.targets.first.target_group.to_s.gsub(/id/, 'arn_suffix')
316
+ }
317
+ end
318
+
319
+ def create_thanos_cloudwatch_alert(service)
320
+ service.load_balancer.targets.each_with_index do |target, i|
321
+ cloudwatch_alarm "#{service.name}_#{i}", 'AWS/NetworkELB', {
322
+ LoadBalancer: output_of('aws_lb', service.load_balancer.name, 'arn_suffix'),
323
+ TargetGroup: target.target_group.to_s.gsub(/id/, 'arn_suffix')
324
+ }
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
@@ -58,7 +58,8 @@ module Terrafying
58
58
  subnets: vpc.subnets.fetch(:private, []),
59
59
  startup_grace_period: 300,
60
60
  depends_on: [],
61
- audit_role: "arn:aws:iam::#{aws.account_id}:role/auditd_logging"
61
+ audit_role: "arn:aws:iam::#{aws.account_id}:role/auditd_logging",
62
+ metrics_ports: [],
62
63
  }.merge(options)
63
64
 
64
65
  unless options[:audit_role].nil?
@@ -117,6 +118,10 @@ module Terrafying
117
118
  @instance_set = add! set.create_in(vpc, name, options.merge(instance_set_options))
118
119
  @security_group = @instance_set.security_group
119
120
 
121
+ if options[:metrics_ports] && !options[:metrics_ports].empty?
122
+ allow_scrape(vpc, options[:metrics_ports], @security_group)
123
+ end
124
+
120
125
  if wants_load_balancer
121
126
  @load_balancer = add! LoadBalancer.create_in(
122
127
  vpc, name, options.merge(
@@ -146,6 +151,21 @@ module Terrafying
146
151
  self
147
152
  end
148
153
 
154
+ def allow_scrape(vpc, ports, security_group)
155
+ prom = Prometheus.find_in(vpc)
156
+ ports.each do |port|
157
+ sg_rule_ident = Digest::SHA256.hexdigest("#{vpc.name}-#{port}-#{security_group}-#{prom.security_group}")
158
+ resource :aws_security_group_rule, sg_rule_ident, {
159
+ security_group_id: security_group,
160
+ type: 'ingress',
161
+ from_port: port,
162
+ to_port: port,
163
+ protocol: 'tcp',
164
+ source_security_group_id: prom.security_group
165
+ }
166
+ end
167
+ end
168
+
149
169
  def with_endpoint_service(options = {})
150
170
  add! EndpointService.create_for(@load_balancer, @name, {
151
171
  fqdn: @domain_names[0],
@@ -1,5 +1,5 @@
1
1
  module Terrafying
2
2
  module Components
3
- VERSION = "1.9.4"
3
+ VERSION = "1.10.0"
4
4
  end
5
5
  end
@@ -55,7 +55,9 @@ module Terrafying
55
55
  }
56
56
 
57
57
  if options[:vpc]
58
- zone_config[:vpc_id] = options[:vpc].id
58
+ zone_config[:vpc] = {
59
+ vpc_id: options[:vpc].id
60
+ }
59
61
 
60
62
  ident = tf_safe("#{options[:vpc].name}-#{ident}")
61
63
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terrafying-components
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.4
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - uSwitch Limited
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-26 00:00:00.000000000 Z
11
+ date: 2018-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,6 +114,7 @@ files:
114
114
  - lib/terrafying/components/letsencrypt.rb
115
115
  - lib/terrafying/components/loadbalancer.rb
116
116
  - lib/terrafying/components/ports.rb
117
+ - lib/terrafying/components/prometheus.rb
117
118
  - lib/terrafying/components/selfsignedca.rb
118
119
  - lib/terrafying/components/service.rb
119
120
  - lib/terrafying/components/staticset.rb