capistrano-asg-rolling 0.7.0 → 0.9.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 +4 -4
- data/.github/workflows/test.yml +1 -1
- data/.rubocop.yml +4 -1
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile.lock +39 -36
- data/LICENSE.txt +1 -1
- data/README.md +71 -35
- data/capistrano-asg-rolling.gemspec +5 -6
- data/lib/capistrano/asg/rolling/ami.rb +10 -3
- data/lib/capistrano/asg/rolling/autoscale_group.rb +9 -7
- data/lib/capistrano/asg/rolling/autoscale_groups.rb +17 -1
- data/lib/capistrano/asg/rolling/aws.rb +7 -3
- data/lib/capistrano/asg/rolling/configuration.rb +43 -13
- data/lib/capistrano/asg/rolling/exception.rb +5 -0
- data/lib/capistrano/asg/rolling/instances.rb +4 -2
- data/lib/capistrano/asg/rolling/launch_templates.rb +10 -8
- data/lib/capistrano/asg/rolling/logger.rb +8 -2
- data/lib/capistrano/asg/rolling/parallel.rb +19 -3
- data/lib/capistrano/asg/rolling/plugin.rb +5 -3
- data/lib/capistrano/asg/rolling/ssh.rb +1 -1
- data/lib/capistrano/asg/rolling/version.rb +1 -1
- data/lib/capistrano/asg/rolling.rb +3 -2
- data/lib/capistrano/asg/tasks/rolling.rake +65 -42
- metadata +20 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2365c9ab16d5af20fe6502ec5ddb678b71d50516a6e9e514b87a94a6a34f60b
|
|
4
|
+
data.tar.gz: ab344dc87be037b41e0e56e18cdc3532339a0e368983cac5ce8c3707d8b82729
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fac5e7d23d81518e9e71924b2596be4e630ab1b38d5f57a481c49bfe71abbb5ea704465e22364957998aa00373420d5f7b8ce3805fd0ba05c5ca97b1f373bb3a
|
|
7
|
+
data.tar.gz: 007da3693ba149357ffe55e600d772c52984295e432eb79b65ecb5696e0861572e0776aa36b40ff2adaaee758209337c4406683bc88cb313e064aea1b82d0c93
|
data/.github/workflows/test.yml
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -5,7 +5,7 @@ plugins:
|
|
|
5
5
|
|
|
6
6
|
AllCops:
|
|
7
7
|
NewCops: enable
|
|
8
|
-
TargetRubyVersion: 3.
|
|
8
|
+
TargetRubyVersion: 3.2
|
|
9
9
|
DisplayCopNames: true
|
|
10
10
|
DisplayStyleGuide: true
|
|
11
11
|
|
|
@@ -39,5 +39,8 @@ RSpec/IndexedLet:
|
|
|
39
39
|
RSpec/MultipleExpectations:
|
|
40
40
|
Max: 5
|
|
41
41
|
|
|
42
|
+
RSpec/MultipleMemoizedHelpers:
|
|
43
|
+
Max: 11
|
|
44
|
+
|
|
42
45
|
Style/GuardClause:
|
|
43
46
|
Enabled: false
|
data/CODE_OF_CONDUCT.md
CHANGED
|
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
|
|
55
55
|
## Enforcement
|
|
56
56
|
|
|
57
57
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
-
reported by contacting the project team at
|
|
58
|
+
reported by contacting the project team at tech-arnhem@iraiser.eu. All
|
|
59
59
|
complaints will be reviewed and investigated and will result in a response that
|
|
60
60
|
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
61
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
capistrano-asg-rolling (0.
|
|
4
|
+
capistrano-asg-rolling (0.9.0)
|
|
5
5
|
aws-sdk-autoscaling (~> 1, >= 1.100.0)
|
|
6
6
|
aws-sdk-ec2 (~> 1)
|
|
7
7
|
capistrano (~> 3)
|
|
@@ -10,17 +10,17 @@ PATH
|
|
|
10
10
|
GEM
|
|
11
11
|
remote: https://rubygems.org/
|
|
12
12
|
specs:
|
|
13
|
-
addressable (2.
|
|
13
|
+
addressable (2.9.0)
|
|
14
14
|
public_suffix (>= 2.0.2, < 8.0)
|
|
15
|
-
airbrussh (1.6.
|
|
15
|
+
airbrussh (1.6.2)
|
|
16
16
|
sshkit (>= 1.6.1, != 1.7.0)
|
|
17
17
|
ast (2.4.3)
|
|
18
18
|
aws-eventstream (1.4.0)
|
|
19
|
-
aws-partitions (1.
|
|
20
|
-
aws-sdk-autoscaling (1.
|
|
21
|
-
aws-sdk-core (~> 3, >= 3.
|
|
19
|
+
aws-partitions (1.1282.0)
|
|
20
|
+
aws-sdk-autoscaling (1.166.0)
|
|
21
|
+
aws-sdk-core (~> 3, >= 3.254.0)
|
|
22
22
|
aws-sigv4 (~> 1.5)
|
|
23
|
-
aws-sdk-core (3.
|
|
23
|
+
aws-sdk-core (3.254.1)
|
|
24
24
|
aws-eventstream (~> 1, >= 1.3.0)
|
|
25
25
|
aws-partitions (~> 1, >= 1.992.0)
|
|
26
26
|
aws-sigv4 (~> 1.9)
|
|
@@ -28,47 +28,47 @@ GEM
|
|
|
28
28
|
bigdecimal
|
|
29
29
|
jmespath (~> 1, >= 1.6.1)
|
|
30
30
|
logger
|
|
31
|
-
aws-sdk-ec2 (1.
|
|
32
|
-
aws-sdk-core (~> 3, >= 3.
|
|
31
|
+
aws-sdk-ec2 (1.641.0)
|
|
32
|
+
aws-sdk-core (~> 3, >= 3.254.0)
|
|
33
33
|
aws-sigv4 (~> 1.5)
|
|
34
34
|
aws-sigv4 (1.12.1)
|
|
35
35
|
aws-eventstream (~> 1, >= 1.0.2)
|
|
36
36
|
base64 (0.3.0)
|
|
37
|
-
bigdecimal (4.
|
|
38
|
-
capistrano (3.20.
|
|
37
|
+
bigdecimal (4.1.2)
|
|
38
|
+
capistrano (3.20.1)
|
|
39
39
|
airbrussh (>= 1.0.0)
|
|
40
40
|
i18n
|
|
41
41
|
rake (>= 10.0.0)
|
|
42
42
|
sshkit (>= 1.9.0)
|
|
43
|
-
concurrent-ruby (1.3.
|
|
43
|
+
concurrent-ruby (1.3.8)
|
|
44
44
|
crack (1.0.1)
|
|
45
45
|
bigdecimal
|
|
46
46
|
rexml
|
|
47
47
|
diff-lcs (1.6.2)
|
|
48
48
|
hashdiff (1.2.1)
|
|
49
|
-
i18n (1.
|
|
49
|
+
i18n (1.15.2)
|
|
50
50
|
concurrent-ruby (~> 1.0)
|
|
51
51
|
jmespath (1.6.2)
|
|
52
|
-
json (2.
|
|
53
|
-
language_server-protocol (3.17.0.
|
|
52
|
+
json (2.21.2)
|
|
53
|
+
language_server-protocol (3.17.0.6)
|
|
54
54
|
lint_roller (1.1.0)
|
|
55
55
|
logger (1.7.0)
|
|
56
56
|
net-scp (4.1.0)
|
|
57
57
|
net-ssh (>= 2.6.5, < 8.0.0)
|
|
58
58
|
net-sftp (4.0.0)
|
|
59
59
|
net-ssh (>= 5.0.0, < 8.0.0)
|
|
60
|
-
net-ssh (7.3.
|
|
60
|
+
net-ssh (7.3.3)
|
|
61
61
|
ostruct (0.6.3)
|
|
62
|
-
parallel (1.
|
|
63
|
-
parser (3.3.
|
|
62
|
+
parallel (1.28.0)
|
|
63
|
+
parser (3.3.12.0)
|
|
64
64
|
ast (~> 2.4.1)
|
|
65
65
|
racc
|
|
66
|
-
prism (1.
|
|
67
|
-
public_suffix (
|
|
66
|
+
prism (1.9.0)
|
|
67
|
+
public_suffix (7.0.5)
|
|
68
68
|
racc (1.8.1)
|
|
69
69
|
rainbow (3.1.1)
|
|
70
|
-
rake (13.
|
|
71
|
-
regexp_parser (2.
|
|
70
|
+
rake (13.4.2)
|
|
71
|
+
regexp_parser (2.12.0)
|
|
72
72
|
rexml (3.4.4)
|
|
73
73
|
rspec (3.13.2)
|
|
74
74
|
rspec-core (~> 3.13.0)
|
|
@@ -79,36 +79,38 @@ GEM
|
|
|
79
79
|
rspec-expectations (3.13.5)
|
|
80
80
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
81
81
|
rspec-support (~> 3.13.0)
|
|
82
|
-
rspec-mocks (3.13.
|
|
82
|
+
rspec-mocks (3.13.8)
|
|
83
83
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
84
84
|
rspec-support (~> 3.13.0)
|
|
85
|
-
rspec-support (3.13.
|
|
86
|
-
rubocop (1.
|
|
87
|
-
json (
|
|
85
|
+
rspec-support (3.13.7)
|
|
86
|
+
rubocop (1.90.0)
|
|
87
|
+
json (>= 2.3)
|
|
88
88
|
language_server-protocol (~> 3.17.0.2)
|
|
89
89
|
lint_roller (~> 1.1.0)
|
|
90
|
-
parallel (
|
|
90
|
+
parallel (>= 1.10)
|
|
91
91
|
parser (>= 3.3.0.2)
|
|
92
92
|
rainbow (>= 2.2.2, < 4.0)
|
|
93
93
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
94
|
-
rubocop-ast (>= 1.
|
|
94
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
95
95
|
ruby-progressbar (~> 1.7)
|
|
96
96
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
97
|
-
rubocop-ast (1.
|
|
97
|
+
rubocop-ast (1.50.0)
|
|
98
98
|
parser (>= 3.3.7.2)
|
|
99
|
-
prism (~> 1.
|
|
100
|
-
rubocop-performance (1.
|
|
99
|
+
prism (~> 1.7)
|
|
100
|
+
rubocop-performance (1.27.0)
|
|
101
101
|
lint_roller (~> 1.1)
|
|
102
|
-
rubocop (>= 1.
|
|
102
|
+
rubocop (>= 1.89.0, < 2.0)
|
|
103
103
|
rubocop-ast (>= 1.47.1, < 2.0)
|
|
104
104
|
rubocop-rake (0.7.1)
|
|
105
105
|
lint_roller (~> 1.1)
|
|
106
106
|
rubocop (>= 1.72.1)
|
|
107
|
-
rubocop-rspec (3.
|
|
107
|
+
rubocop-rspec (3.10.2)
|
|
108
108
|
lint_roller (~> 1.1)
|
|
109
|
-
|
|
109
|
+
regexp_parser (>= 2.0)
|
|
110
|
+
rubocop (~> 1.86, >= 1.86.2)
|
|
110
111
|
ruby-progressbar (1.13.0)
|
|
111
|
-
|
|
112
|
+
simplecov (1.1.1)
|
|
113
|
+
sshkit (1.25.1)
|
|
112
114
|
base64
|
|
113
115
|
logger
|
|
114
116
|
net-scp (>= 1.1.2)
|
|
@@ -118,7 +120,7 @@ GEM
|
|
|
118
120
|
unicode-display_width (3.2.0)
|
|
119
121
|
unicode-emoji (~> 4.1)
|
|
120
122
|
unicode-emoji (4.2.0)
|
|
121
|
-
webmock (3.26.
|
|
123
|
+
webmock (3.26.3)
|
|
122
124
|
addressable (>= 2.8.0)
|
|
123
125
|
crack (>= 0.3.2)
|
|
124
126
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
@@ -135,6 +137,7 @@ DEPENDENCIES
|
|
|
135
137
|
rubocop-performance (~> 1.20)
|
|
136
138
|
rubocop-rake (~> 0.6)
|
|
137
139
|
rubocop-rspec (~> 3.0)
|
|
140
|
+
simplecov (~> 1.0)
|
|
138
141
|
webmock (~> 3.11)
|
|
139
142
|
|
|
140
143
|
BUNDLED WITH
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups using the [instance refresh feature](https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-instance-refresh.html).
|
|
8
8
|
|
|
9
|
-
Instead of deploying to live servers, capistrano-asg-rolling will create
|
|
9
|
+
Instead of deploying to live servers, capistrano-asg-rolling will create temporary instances for deployment and then trigger an instance refresh to perform a rolling update of the Auto Scaling Group(s). In more detail, during deployment it will:
|
|
10
10
|
|
|
11
|
-
- Launch an instance from the AMI defined in
|
|
11
|
+
- Launch an instance for each Auto Scaling Group, from the AMI defined in its Launch Template.
|
|
12
12
|
- Deploy your application to the launched instances.
|
|
13
13
|
- After deployment, stop the instances and create an AMI for each instance.
|
|
14
14
|
- Create new Launch Template versions for the new AMIs.
|
|
15
15
|
- Trigger Instance Refresh on the Auto Scaling Group(s) to perform a rolling update.
|
|
16
16
|
- Delete any outdated Launch Template versions, AMIs and snapshots created by previous deployments.
|
|
17
|
-
- Terminate the
|
|
17
|
+
- Terminate the temporary instances.
|
|
18
18
|
|
|
19
19
|
## Important
|
|
20
20
|
|
|
@@ -25,13 +25,13 @@ https://docs.aws.amazon.com/autoscaling/ec2/userguide/instance-refresh-overview.
|
|
|
25
25
|
|
|
26
26
|
### Launch Templates
|
|
27
27
|
|
|
28
|
-
This gem depends on Auto Scaling Groups with Launch Templates. Using an Auto Scaling Group with a Launch Configuration is not supported, and will raise
|
|
28
|
+
This gem depends on Auto Scaling Groups with Launch Templates. Using an Auto Scaling Group with a Launch Configuration is not supported, and will raise a `Capistrano::ASG::Rolling::NoLaunchTemplate` error.
|
|
29
29
|
|
|
30
|
-
Instance refresh uses the desired configuration to update the Launch Template version of the Auto Scaling Group after a
|
|
30
|
+
Instance refresh uses the desired configuration to update the Launch Template version of the Auto Scaling Group after a successful deployment. Setting the Launch Template version to `Latest` on the Auto Scaling Group is not needed.
|
|
31
31
|
|
|
32
32
|
### Experimental
|
|
33
33
|
|
|
34
|
-
Please note that this gem works well for our configuration / use case, but it might not fit yours. Any feedback
|
|
34
|
+
Please note that this gem works well for our configuration / use case, but it might not fit yours. Any feedback via GitHub issues / pull requests is much appreciated.
|
|
35
35
|
|
|
36
36
|
## Installation
|
|
37
37
|
|
|
@@ -59,60 +59,96 @@ install_plugin Capistrano::ASG::Rolling::Plugin
|
|
|
59
59
|
|
|
60
60
|
## Configuration
|
|
61
61
|
|
|
62
|
-
Below
|
|
62
|
+
Below you can find all configurable settings for this gem.
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
### AWS
|
|
65
|
+
|
|
66
|
+
Set up the AWS credentials and region:
|
|
65
67
|
|
|
66
68
|
```ruby
|
|
67
69
|
# config/deploy.rb
|
|
68
70
|
set :aws_access_key_id, ENV['AWS_ACCESS_KEY_ID']
|
|
69
71
|
set :aws_secret_access_key, ENV['AWS_SECRET_ACCESS_KEY']
|
|
72
|
+
set :aws_session_token, ENV['AWS_SESSION_TOKEN']
|
|
70
73
|
set :aws_region, ENV['AWS_REGION']
|
|
71
74
|
```
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
The AWS clients are configured with `adaptive` retry mode and a retry limit of 10 by default, so transient throttling (`Aws::AutoScaling::Errors::Throttling: Rate exceeded`) does not abort a deployment. You can tune both:
|
|
74
77
|
|
|
75
78
|
```ruby
|
|
76
79
|
# config/deploy.rb
|
|
77
|
-
set :
|
|
80
|
+
set :asg_aws_retry_mode, 'adaptive' # default; one of 'legacy', 'standard', 'adaptive'
|
|
81
|
+
set :asg_aws_retry_limit, 10 # default
|
|
78
82
|
```
|
|
79
83
|
|
|
80
|
-
|
|
84
|
+
### Launched instances
|
|
85
|
+
|
|
86
|
+
Launched instances are added to Capistrano as servers using their private IP address by default. Set this option to false to use the public IP address instead:
|
|
81
87
|
|
|
82
88
|
```ruby
|
|
83
89
|
# config/deploy.rb
|
|
84
90
|
set :asg_rolling_use_private_ip_address, false
|
|
85
91
|
```
|
|
86
92
|
|
|
87
|
-
|
|
93
|
+
When launching an instance, you can override any settings defined in the Launch Template with:
|
|
88
94
|
|
|
89
95
|
```ruby
|
|
90
96
|
# config/deploy.rb
|
|
91
|
-
set :
|
|
97
|
+
set :asg_rolling_instance_overrides, { instance_type: 'c5.large' }
|
|
92
98
|
```
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
### AMI
|
|
101
|
+
|
|
102
|
+
After creating an AMI, the gem waits until it becomes available before triggering an instance refresh. The defaults match the AWS SDK defaults (~10 minutes), but for larger root volumes (e.g. resizing 24 GB → 32 GB) the AMI can take longer to become available. Override the waiter via:
|
|
95
103
|
|
|
96
104
|
```ruby
|
|
97
105
|
# config/deploy.rb
|
|
98
|
-
set :
|
|
106
|
+
set :asg_ami_wait_delay, 15 # seconds between polls (default: 15)
|
|
107
|
+
set :asg_ami_wait_max_attempts, 40 # max polls (default: 40, ≈ 10 minutes)
|
|
99
108
|
```
|
|
100
109
|
|
|
101
|
-
|
|
110
|
+
### Instance refresh
|
|
111
|
+
|
|
112
|
+
You can make Capistrano wait until the instance refresh on the Auto Scaling Group(s) has completed:
|
|
102
113
|
|
|
103
114
|
```ruby
|
|
104
115
|
# config/deploy.rb
|
|
105
116
|
set :asg_wait_for_instance_refresh, true
|
|
106
|
-
set :asg_instance_refresh_polling_interval, 30 # default
|
|
117
|
+
set :asg_instance_refresh_polling_interval, 30 # seconds (default: 30)
|
|
107
118
|
```
|
|
108
119
|
|
|
109
|
-
Enable
|
|
120
|
+
Enable auto-rollback to have the Auto Scaling Group roll back to its previous configuration when the instance refresh fails (default: false):
|
|
110
121
|
|
|
111
122
|
```ruby
|
|
112
123
|
# config/deploy.rb
|
|
113
124
|
set :asg_instance_refresh_auto_rollback, true
|
|
114
125
|
```
|
|
115
126
|
|
|
127
|
+
### Cleanup
|
|
128
|
+
|
|
129
|
+
After deployment, any outdated Launch Template versions, AMIs and snapshots will be deleted. By default, the same number of versions as Capistrano's `keep_releases` will be kept. Change this with:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# config/deploy.rb
|
|
133
|
+
set :asg_rolling_keep_versions, 10
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Logging
|
|
137
|
+
|
|
138
|
+
Verbose logging is enabled by default; set this option to false for less verbose logging:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
# config/deploy.rb
|
|
142
|
+
set :asg_rolling_log_verbose, false
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
By default, the log output does not contain any timestamps; set the option below to true to enable timestamps:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
# config/deploy.rb
|
|
149
|
+
set :asg_rolling_log_timestamp, true
|
|
150
|
+
```
|
|
151
|
+
|
|
116
152
|
## Usage
|
|
117
153
|
|
|
118
154
|
Specify the Auto Scaling Groups with the keyword `autoscale` instead of using the `server` keyword in Capistrano's stage configuration. Provide the name of the Auto Scaling Group and any properties you want to pass to the server:
|
|
@@ -129,7 +165,7 @@ Now start a deployment with `cap <stage> deploy` and enjoy.
|
|
|
129
165
|
|
|
130
166
|
There might be cases where you just want to deploy your code to the servers in the Auto Scaling Group(s) without a rolling update.
|
|
131
167
|
|
|
132
|
-
You can configure rolling updates per
|
|
168
|
+
You can configure rolling updates per Auto Scaling Group by using the `rolling` option:
|
|
133
169
|
|
|
134
170
|
```ruby
|
|
135
171
|
# config/deploy/<stage>.rb
|
|
@@ -137,28 +173,28 @@ autoscale 'app-autoscale-group', rolling: true # default: use rolling deploym
|
|
|
137
173
|
autoscale 'web-autoscale-group', rolling: false # override: use normal deployment
|
|
138
174
|
```
|
|
139
175
|
|
|
140
|
-
### Deploy with custom
|
|
176
|
+
### Deploy with custom minimum/maximum healthy percentages
|
|
141
177
|
|
|
142
178
|
The instance refresh is triggered without specifying a value for the minimum / maximum healthy percentages. This means that either the default
|
|
143
179
|
values will be used (minimum: 90%, maximum: 100%) or the percentages set in the instance maintenance policy for the Auto Scaling Group.
|
|
144
|
-
You can tune both the minimum and maximum values to have more control
|
|
180
|
+
You can tune both the minimum and maximum values to have more control over the desired capacity that must be healthy to proceed with replacing instances.
|
|
145
181
|
|
|
146
|
-
For example
|
|
182
|
+
For example, reducing the minimum healthy percentage allows more instances to be terminated and replaced at once during the instance refresh; a value of 0 would terminate and replace all instances in the Auto Scaling Group at once.
|
|
147
183
|
|
|
148
|
-
You can configure the minimum healthy percentage per
|
|
184
|
+
You can configure the minimum healthy percentage per Auto Scaling Group using the `min_healthy_percentage` option, and the maximum healthy percentage using the `max_healthy_percentage` option. Please note that if you specify `max_healthy_percentage`, you must also specify `min_healthy_percentage`.
|
|
149
185
|
|
|
150
186
|
```ruby
|
|
151
187
|
# config/deploy/<stage>.rb
|
|
152
|
-
autoscale 'app-autoscale-group'
|
|
188
|
+
autoscale 'app-autoscale-group' # use default percentages or percentages set in the instance maintenance policy
|
|
153
189
|
autoscale 'web-autoscale-group', min_healthy_percentage: 75 # allow 25% of instances to be terminated and replaced at once
|
|
154
190
|
autoscale 'web-autoscale-group', min_healthy_percentage: 100, max_healthy_percentage: 110 # allow for 10% above desired capacity during instance refresh
|
|
155
191
|
```
|
|
156
192
|
|
|
157
193
|
### Custom stage
|
|
158
194
|
|
|
159
|
-
The rolling configuration of the stage has a side-effect: any Capistrano
|
|
195
|
+
The rolling configuration of the stage has a side-effect: any Capistrano task you run will also launch instances per Auto Scaling Group.
|
|
160
196
|
|
|
161
|
-
For example the command
|
|
197
|
+
For example, the command `cap production rails:console` will launch a new instance and run `rails:console` on that instance. While that can be useful, you often just want to run the task on the primary server. A solution is to create two stages with different rolling configurations, for example:
|
|
162
198
|
|
|
163
199
|
```ruby
|
|
164
200
|
# config/deploy/production.rb
|
|
@@ -183,7 +219,7 @@ With these two stages, you can run any tasks with `cap production <task name>` a
|
|
|
183
219
|
|
|
184
220
|
### Tags
|
|
185
221
|
|
|
186
|
-
During deployment, the following tags will be added to the AMI and snapshot containing information about the current application, stage and deployment:
|
|
222
|
+
During deployment, the following tags will be added to the AMI and snapshot, containing information about the current application, stage and deployment:
|
|
187
223
|
- `capistrano-asg-rolling:application`
|
|
188
224
|
- `capistrano-asg-rolling:stage`
|
|
189
225
|
- `capistrano-asg-rolling:deployment-branch`
|
|
@@ -192,7 +228,7 @@ During deployment, the following tags will be added to the AMI and snapshot cont
|
|
|
192
228
|
- `capistrano-asg-rolling:deployment-user`
|
|
193
229
|
|
|
194
230
|
In addition to that, the tag `capistrano-asg-rolling:gem-version` will be added with the value of the current gem version.
|
|
195
|
-
This tag is also used to determine
|
|
231
|
+
This tag is also used to determine whether the AMI was created by this gem and can therefore be deleted automatically.
|
|
196
232
|
|
|
197
233
|
You can add custom tag(s) to the AMI and snapshot by setting the property `asg_rolling_ami_tags`, for example:
|
|
198
234
|
|
|
@@ -205,7 +241,7 @@ set :asg_rolling_ami_tags, { 'Application' => 'My Application', 'Environment' =>
|
|
|
205
241
|
|
|
206
242
|
### Test deployment
|
|
207
243
|
|
|
208
|
-
Do a test deployment: run the deploy task, but do not trigger
|
|
244
|
+
Do a test deployment: run the deploy task, but do not trigger an instance refresh and do not automatically terminate the launched instances.
|
|
209
245
|
|
|
210
246
|
```bash
|
|
211
247
|
cap <stage> rolling:deploy_test
|
|
@@ -213,8 +249,8 @@ cap <stage> rolling:deploy_test
|
|
|
213
249
|
|
|
214
250
|
### Launch instances
|
|
215
251
|
|
|
216
|
-
|
|
217
|
-
|
|
252
|
+
Launch instance(s) from the Launch Template of the Auto Scaling Group(s).
|
|
253
|
+
These instances are not attached to the Auto Scaling Group and need to be terminated manually.
|
|
218
254
|
|
|
219
255
|
```bash
|
|
220
256
|
cap <stage> rolling:launch_instances
|
|
@@ -222,8 +258,8 @@ cap <stage> rolling:launch_instances
|
|
|
222
258
|
|
|
223
259
|
### Create AMI of instance in ASG
|
|
224
260
|
|
|
225
|
-
Pick an instance in the Auto
|
|
226
|
-
Then create an AMI
|
|
261
|
+
Pick an instance in the Auto Scaling Group(s), put it into standby and stop it.
|
|
262
|
+
Then create an AMI, start the instance and put it back into service, and create a new Launch Template version with the new AMI.
|
|
227
263
|
|
|
228
264
|
```bash
|
|
229
265
|
cap <stage> rolling:create_ami
|
|
@@ -231,7 +267,7 @@ cap <stage> rolling:create_ami
|
|
|
231
267
|
|
|
232
268
|
## Filtering
|
|
233
269
|
|
|
234
|
-
You can filter any command to run on a specific Auto
|
|
270
|
+
You can filter any command to run on a specific Auto Scaling Group by using the parameter `asg_name`.
|
|
235
271
|
|
|
236
272
|
For example, the command:
|
|
237
273
|
|
|
@@ -239,7 +275,7 @@ For example, the command:
|
|
|
239
275
|
cap <stage> deploy asg_name=app-autoscale-group
|
|
240
276
|
```
|
|
241
277
|
|
|
242
|
-
|
|
278
|
+
This will do a deployment only on the Auto Scaling Group named `app-autoscale-group`.
|
|
243
279
|
|
|
244
280
|
## IAM policy
|
|
245
281
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
-
require 'capistrano/asg/rolling/version'
|
|
3
|
+
require_relative 'lib/capistrano/asg/rolling/version'
|
|
6
4
|
|
|
7
5
|
Gem::Specification.new do |spec|
|
|
8
6
|
spec.name = 'capistrano-asg-rolling'
|
|
9
7
|
spec.version = Capistrano::ASG::Rolling::VERSION
|
|
10
|
-
spec.authors = [
|
|
11
|
-
spec.email = ['
|
|
8
|
+
spec.authors = %w[Kentaa iRaiser]
|
|
9
|
+
spec.email = ['tech-arnhem@iraiser.eu']
|
|
12
10
|
|
|
13
11
|
spec.summary = 'Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups using Instance Refresh'
|
|
14
12
|
spec.homepage = 'https://github.com/KentaaNL/capistrano-asg-rolling'
|
|
@@ -26,11 +24,12 @@ Gem::Specification.new do |spec|
|
|
|
26
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
27
25
|
spec.require_paths = ['lib']
|
|
28
26
|
|
|
29
|
-
spec.required_ruby_version = '>= 3.
|
|
27
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
30
28
|
|
|
31
29
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
32
30
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
33
31
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
32
|
+
spec.add_development_dependency 'simplecov', '~> 1.0'
|
|
34
33
|
spec.add_development_dependency 'webmock', '~> 3.11'
|
|
35
34
|
|
|
36
35
|
spec.add_dependency 'aws-sdk-autoscaling', '~> 1', '>= 1.100.0'
|
|
@@ -36,10 +36,17 @@ module Capistrano
|
|
|
36
36
|
response = aws_ec2_client.create_image(options)
|
|
37
37
|
|
|
38
38
|
begin
|
|
39
|
-
aws_ec2_client.wait_until(:image_available, image_ids: [response.image_id])
|
|
40
|
-
|
|
39
|
+
aws_ec2_client.wait_until(:image_available, image_ids: [response.image_id]) do |waiter|
|
|
40
|
+
waiter.delay = Configuration.ami_wait_delay
|
|
41
|
+
waiter.max_attempts = Configuration.ami_wait_max_attempts
|
|
42
|
+
end
|
|
43
|
+
rescue Aws::Waiters::Errors::TooManyAttemptsError => e
|
|
41
44
|
# When waiting for the AMI takes longer than the default (10 minutes),
|
|
42
|
-
# then assume it will eventually succeed and just continue.
|
|
45
|
+
# then assume it will eventually succeed and just continue. Surface a
|
|
46
|
+
# warning so operators can see that the wait did not complete in time
|
|
47
|
+
# (for example after increasing the root volume size of the source
|
|
48
|
+
# instance) before the subsequent Instance Refresh is started.
|
|
49
|
+
Kernel.warn("WARNING: timed out waiting for AMI #{response.image_id} to become available: #{e.message}")
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
new(response.image_id, instance)
|
|
@@ -21,12 +21,6 @@ module Capistrano
|
|
|
21
21
|
@name = name
|
|
22
22
|
@properties = properties
|
|
23
23
|
|
|
24
|
-
if properties[:healthy_percentage]
|
|
25
|
-
properties[:min_healthy_percentage] = properties.delete(:healthy_percentage)
|
|
26
|
-
|
|
27
|
-
Kernel.warn('WARNING: the property `healthy_percentage` is deprecated and will be removed in a future release. Please update to `min_healthy_percentage`.')
|
|
28
|
-
end
|
|
29
|
-
|
|
30
24
|
validate_properties!
|
|
31
25
|
end
|
|
32
26
|
|
|
@@ -81,7 +75,11 @@ module Capistrano
|
|
|
81
75
|
auto_rollback: auto_rollback
|
|
82
76
|
}.compact
|
|
83
77
|
).instance_refresh_id
|
|
84
|
-
rescue Aws::AutoScaling::Errors::
|
|
78
|
+
rescue Aws::AutoScaling::Errors::ServiceError => e
|
|
79
|
+
# Wrap all AWS Auto Scaling service errors (InstanceRefreshInProgress,
|
|
80
|
+
# ValidationError, throttling, etc.) so the rake task can rescue a
|
|
81
|
+
# single gem-defined error type and continue with the remaining
|
|
82
|
+
# Auto Scaling Group(s).
|
|
85
83
|
raise Capistrano::ASG::Rolling::StartInstanceRefreshError, e
|
|
86
84
|
end
|
|
87
85
|
|
|
@@ -102,6 +100,10 @@ module Capistrano
|
|
|
102
100
|
return nil if status.nil?
|
|
103
101
|
|
|
104
102
|
InstanceRefreshStatus.new(status, percentage_complete)
|
|
103
|
+
rescue Aws::AutoScaling::Errors::ServiceError => e
|
|
104
|
+
# Wrap AWS service errors (typically throttling that exceeded the SDK's
|
|
105
|
+
# retry budget) so callers can rescue a gem-defined error type.
|
|
106
|
+
raise Capistrano::ASG::Rolling::InstanceRefreshStatusError, e
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
# Returns instances with lifecycle state "InService" for this Auto Scaling Group.
|
|
@@ -19,8 +19,24 @@ module Capistrano
|
|
|
19
19
|
@groups.reject { |group| filtered?(group) }.each(&)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def empty?
|
|
23
|
+
none?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rolling
|
|
27
|
+
self.class.new(select(&:rolling?))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def standard
|
|
31
|
+
self.class.new(reject(&:rolling?))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def with_unique_images
|
|
35
|
+
self.class.new(uniq { |group| group.launch_template.image_id })
|
|
36
|
+
end
|
|
37
|
+
|
|
22
38
|
def launch_templates
|
|
23
|
-
templates =
|
|
39
|
+
templates = map(&:launch_template)
|
|
24
40
|
LaunchTemplates.new(templates)
|
|
25
41
|
end
|
|
26
42
|
|
|
@@ -6,14 +6,16 @@ require 'aws-sdk-ec2'
|
|
|
6
6
|
module Capistrano
|
|
7
7
|
module ASG
|
|
8
8
|
module Rolling
|
|
9
|
-
# AWS SDK.
|
|
9
|
+
# AWS SDK helpers. Clients are memoized per object and shared across objects on the same thread.
|
|
10
10
|
module AWS
|
|
11
11
|
def aws_autoscaling_client
|
|
12
|
-
@aws_autoscaling_client ||=
|
|
12
|
+
@aws_autoscaling_client ||=
|
|
13
|
+
Thread.current[:aws_autoscaling_client] ||= ::Aws::AutoScaling::Client.new(aws_options)
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def aws_ec2_client
|
|
16
|
-
@aws_ec2_client ||=
|
|
17
|
+
@aws_ec2_client ||=
|
|
18
|
+
Thread.current[:aws_ec2_client] ||= ::Aws::EC2::Client.new(aws_options)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
private
|
|
@@ -22,6 +24,8 @@ module Capistrano
|
|
|
22
24
|
options = {}
|
|
23
25
|
options[:region] = aws_region if aws_region
|
|
24
26
|
options[:credentials] = aws_credentials if aws_credentials.set?
|
|
27
|
+
options[:retry_mode] = Configuration.aws_retry_mode
|
|
28
|
+
options[:retry_limit] = Configuration.aws_retry_limit
|
|
25
29
|
options[:http_wire_trace] = true if ENV['AWS_HTTP_WIRE_TRACE'] == '1'
|
|
26
30
|
options
|
|
27
31
|
end
|
|
@@ -3,25 +3,39 @@
|
|
|
3
3
|
module Capistrano
|
|
4
4
|
module ASG
|
|
5
5
|
module Rolling
|
|
6
|
-
# Singleton that holds the configuration.
|
|
6
|
+
# Singleton that holds the configuration and global state of the plugin.
|
|
7
|
+
# State is eagerly initialized so accessors are lock-free.
|
|
7
8
|
module Configuration
|
|
8
9
|
extend Capistrano::DSL
|
|
9
10
|
|
|
11
|
+
# Registered Auto Scaling Groups.
|
|
12
|
+
@autoscale_groups = AutoscaleGroups.new
|
|
13
|
+
|
|
14
|
+
# Launched Instances.
|
|
15
|
+
@instances = Instances.new
|
|
16
|
+
|
|
17
|
+
# Updated Launch Templates.
|
|
18
|
+
@launch_templates = LaunchTemplates.new
|
|
19
|
+
|
|
10
20
|
module_function
|
|
11
21
|
|
|
12
|
-
# Registered Auto Scaling Groups.
|
|
13
22
|
def autoscale_groups
|
|
14
|
-
@autoscale_groups
|
|
23
|
+
@autoscale_groups
|
|
15
24
|
end
|
|
16
25
|
|
|
17
|
-
# Launched Instances.
|
|
18
26
|
def instances
|
|
19
|
-
@instances
|
|
27
|
+
@instances
|
|
20
28
|
end
|
|
21
29
|
|
|
22
|
-
# Updated Launch Templates.
|
|
23
30
|
def launch_templates
|
|
24
|
-
@launch_templates
|
|
31
|
+
@launch_templates
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Resets all mutable state.
|
|
35
|
+
def reset!
|
|
36
|
+
@autoscale_groups = AutoscaleGroups.new
|
|
37
|
+
@instances = Instances.new
|
|
38
|
+
@launch_templates = LaunchTemplates.new
|
|
25
39
|
end
|
|
26
40
|
|
|
27
41
|
def aws_access_key_id
|
|
@@ -44,10 +58,6 @@ module Capistrano
|
|
|
44
58
|
fetch(:asg_rolling_group_name)
|
|
45
59
|
end
|
|
46
60
|
|
|
47
|
-
def ssh_options
|
|
48
|
-
fetch(:asg_rolling_ssh_options, fetch(:ssh_options))
|
|
49
|
-
end
|
|
50
|
-
|
|
51
61
|
def instance_overrides
|
|
52
62
|
fetch(:asg_rolling_instance_overrides)
|
|
53
63
|
end
|
|
@@ -60,8 +70,12 @@ module Capistrano
|
|
|
60
70
|
fetch(:asg_rolling_keep_versions, fetch(:keep_releases))
|
|
61
71
|
end
|
|
62
72
|
|
|
63
|
-
def
|
|
64
|
-
fetch(:
|
|
73
|
+
def log_verbose?
|
|
74
|
+
fetch(:asg_rolling_log_verbose)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def log_timestamp?
|
|
78
|
+
fetch(:asg_rolling_log_timestamp)
|
|
65
79
|
end
|
|
66
80
|
|
|
67
81
|
def rolling_update=(value)
|
|
@@ -79,6 +93,22 @@ module Capistrano
|
|
|
79
93
|
def instance_refresh_polling_interval
|
|
80
94
|
fetch(:asg_instance_refresh_polling_interval, 30)
|
|
81
95
|
end
|
|
96
|
+
|
|
97
|
+
def aws_retry_mode
|
|
98
|
+
fetch(:asg_aws_retry_mode, 'adaptive')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def aws_retry_limit
|
|
102
|
+
fetch(:asg_aws_retry_limit, 10)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def ami_wait_delay
|
|
106
|
+
fetch(:asg_ami_wait_delay, 15)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def ami_wait_max_attempts
|
|
110
|
+
fetch(:asg_ami_wait_max_attempts, 40)
|
|
111
|
+
end
|
|
82
112
|
end
|
|
83
113
|
end
|
|
84
114
|
end
|
|
@@ -13,9 +13,14 @@ module Capistrano
|
|
|
13
13
|
class NoLaunchTemplate < Capistrano::ASG::Rolling::Exception
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Exception when starting an instance refresh failed.
|
|
16
17
|
class StartInstanceRefreshError < Capistrano::ASG::Rolling::Exception
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
# Exception when fetching the instance refresh status failed.
|
|
21
|
+
class InstanceRefreshStatusError < Capistrano::ASG::Rolling::Exception
|
|
22
|
+
end
|
|
23
|
+
|
|
19
24
|
# Exception when the instance refresh failed on one of the ASGs.
|
|
20
25
|
class InstanceRefreshFailed < Capistrano::ASG::Rolling::Exception
|
|
21
26
|
def initialize
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'concurrent/array'
|
|
4
|
+
|
|
3
5
|
module Capistrano
|
|
4
6
|
module ASG
|
|
5
7
|
module Rolling
|
|
@@ -8,7 +10,7 @@ module Capistrano
|
|
|
8
10
|
include Enumerable
|
|
9
11
|
|
|
10
12
|
def initialize(instances = [])
|
|
11
|
-
@instances = instances
|
|
13
|
+
@instances = Concurrent::Array.new(instances)
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def <<(instance)
|
|
@@ -23,7 +25,7 @@ module Capistrano
|
|
|
23
25
|
instances.empty?
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
def
|
|
28
|
+
def auto_terminatable
|
|
27
29
|
self.class.new(select(&:auto_terminate?))
|
|
28
30
|
end
|
|
29
31
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'set'
|
|
4
|
-
|
|
5
3
|
module Capistrano
|
|
6
4
|
module ASG
|
|
7
5
|
module Rolling
|
|
@@ -29,16 +27,20 @@ module Capistrano
|
|
|
29
27
|
@templates.empty?
|
|
30
28
|
end
|
|
31
29
|
|
|
30
|
+
# For each AMI, find templates still pointing at the old image it was built
|
|
31
|
+
# from, then create new template versions referencing the new AMI in parallel.
|
|
32
|
+
#
|
|
33
|
+
# Returns `LaunchTemplates` containing only the newly created versions.
|
|
32
34
|
def update(amis:, description: nil)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
amis.each do |ami|
|
|
35
|
+
template_image_pairs = amis.flat_map do |ami|
|
|
36
36
|
old_image_id = ami.instance.image_id
|
|
37
37
|
new_image_id = ami.id
|
|
38
38
|
|
|
39
|
-
with_image(old_image_id).
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
with_image(old_image_id).map { |template| [template, new_image_id] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
updated_templates = Parallel.run(template_image_pairs) do |template, new_image_id|
|
|
43
|
+
template.create_version(image_id: new_image_id, description: description)
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
self.class.new(updated_templates)
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
module Capistrano
|
|
4
4
|
module ASG
|
|
5
5
|
module Rolling
|
|
6
|
-
#
|
|
6
|
+
# Colorized stdout/stderr output with optional timestamps and **bold** formatting.
|
|
7
7
|
class Logger
|
|
8
|
-
def initialize(verbose: false)
|
|
8
|
+
def initialize(timestamp: false, verbose: false)
|
|
9
|
+
@timestamp = timestamp
|
|
9
10
|
@verbose = verbose
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -28,6 +29,7 @@ module Capistrano
|
|
|
28
29
|
private
|
|
29
30
|
|
|
30
31
|
def format_text(text, color: nil)
|
|
32
|
+
text = "[#{current_time}] #{text}" if @timestamp
|
|
31
33
|
text = colorize_text(text, color) if color
|
|
32
34
|
text.gsub(/\*\*(.+?)\*\*/, bold_text('\\1'))
|
|
33
35
|
end
|
|
@@ -43,6 +45,10 @@ module Capistrano
|
|
|
43
45
|
def _color
|
|
44
46
|
@_color ||= SSHKit::Color.new($stdout)
|
|
45
47
|
end
|
|
48
|
+
|
|
49
|
+
def current_time
|
|
50
|
+
Time.now.strftime('%H:%M:%S')
|
|
51
|
+
end
|
|
46
52
|
end
|
|
47
53
|
end
|
|
48
54
|
end
|
|
@@ -9,18 +9,34 @@ module Capistrano
|
|
|
9
9
|
module Parallel
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
|
+
# Runs the given block once per element of `work`, in parallel.
|
|
13
|
+
#
|
|
14
|
+
# All threads are joined before this method returns, so a failure in one
|
|
15
|
+
# block does not abandon the other in-flight threads. If any block
|
|
16
|
+
# raises, the first error is re-raised after all threads have completed;
|
|
17
|
+
# additional errors are surfaced via `Kernel.warn`.
|
|
12
18
|
def run(work)
|
|
13
|
-
|
|
19
|
+
results = Concurrent::Array.new
|
|
20
|
+
errors = Concurrent::Array.new
|
|
14
21
|
|
|
15
22
|
threads = work.map do |w|
|
|
16
23
|
Thread.new do
|
|
17
|
-
|
|
24
|
+
results << yield(w)
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
errors << e
|
|
18
27
|
end
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
threads.each(&:join)
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
if errors.any?
|
|
33
|
+
errors.drop(1).each do |e|
|
|
34
|
+
Kernel.warn("WARNING: parallel task failed: #{e.class}: #{e.message}")
|
|
35
|
+
end
|
|
36
|
+
raise errors.first
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
results
|
|
24
40
|
end
|
|
25
41
|
end
|
|
26
42
|
end
|
|
@@ -14,7 +14,8 @@ module Capistrano
|
|
|
14
14
|
def set_defaults
|
|
15
15
|
set_if_empty :asg_rolling_group_name, ENV.fetch('asg_name', nil)
|
|
16
16
|
set_if_empty :asg_rolling_use_private_ip_address, true
|
|
17
|
-
set_if_empty :
|
|
17
|
+
set_if_empty :asg_rolling_log_verbose, true
|
|
18
|
+
set_if_empty :asg_rolling_log_timestamp, false
|
|
18
19
|
set_if_empty :asg_rolling_update, true
|
|
19
20
|
end
|
|
20
21
|
|
|
@@ -28,6 +29,7 @@ module Capistrano
|
|
|
28
29
|
|
|
29
30
|
after 'rolling:update', 'rolling:cleanup'
|
|
30
31
|
after 'rolling:create_ami', 'rolling:cleanup'
|
|
32
|
+
|
|
31
33
|
after 'rolling:update', 'rolling:instance_refresh_status'
|
|
32
34
|
after 'rolling:trigger_instance_refresh', 'rolling:instance_refresh_status'
|
|
33
35
|
|
|
@@ -41,7 +43,7 @@ module Capistrano
|
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
def logger
|
|
44
|
-
@logger ||= Logger.new(verbose: config.
|
|
46
|
+
@logger ||= Logger.new(timestamp: config.log_timestamp?, verbose: config.log_verbose?)
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
def config
|
|
@@ -59,7 +61,7 @@ module Capistrano
|
|
|
59
61
|
|
|
60
62
|
# Make sure any instances that were launched for rolling updates are terminated.
|
|
61
63
|
def cleanup
|
|
62
|
-
instances = config.instances.
|
|
64
|
+
instances = config.instances.auto_terminatable
|
|
63
65
|
return if instances.empty?
|
|
64
66
|
|
|
65
67
|
logger.info 'Terminating instance(s)...'
|
|
@@ -6,7 +6,6 @@ require 'capistrano/asg/rolling/aws'
|
|
|
6
6
|
require 'capistrano/asg/rolling/ami'
|
|
7
7
|
require 'capistrano/asg/rolling/autoscale_group'
|
|
8
8
|
require 'capistrano/asg/rolling/autoscale_groups'
|
|
9
|
-
require 'capistrano/asg/rolling/configuration'
|
|
10
9
|
require 'capistrano/asg/rolling/dsl'
|
|
11
10
|
require 'capistrano/asg/rolling/exception'
|
|
12
11
|
require 'capistrano/asg/rolling/instance'
|
|
@@ -15,8 +14,10 @@ require 'capistrano/asg/rolling/launch_template'
|
|
|
15
14
|
require 'capistrano/asg/rolling/launch_templates'
|
|
16
15
|
require 'capistrano/asg/rolling/logger'
|
|
17
16
|
require 'capistrano/asg/rolling/parallel'
|
|
18
|
-
require 'capistrano/asg/rolling/plugin'
|
|
19
17
|
require 'capistrano/asg/rolling/snapshot'
|
|
20
18
|
require 'capistrano/asg/rolling/ssh'
|
|
21
19
|
require 'capistrano/asg/rolling/tags'
|
|
22
20
|
require 'capistrano/asg/rolling/version'
|
|
21
|
+
|
|
22
|
+
require 'capistrano/asg/rolling/configuration'
|
|
23
|
+
require 'capistrano/asg/rolling/plugin'
|
|
@@ -2,35 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
namespace :rolling do
|
|
4
4
|
desc 'Setup servers to be used for (rolling) deployment'
|
|
5
|
-
task :
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
task setup: %w[rolling:prepare rolling:wait_for_ssh]
|
|
6
|
+
|
|
7
|
+
desc 'Resolve Auto Scaling Groups, launch rolling instances, and register all servers'
|
|
8
|
+
task :prepare do
|
|
9
|
+
rolling_groups = config.autoscale_groups.rolling
|
|
10
|
+
if rolling_groups.any?
|
|
11
|
+
rolling_groups.each do |group|
|
|
8
12
|
logger.info "Auto Scaling Group: **#{group.name}**, rolling deployment strategy."
|
|
13
|
+
end
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
launched_instances = Capistrano::ASG::Rolling::Parallel.run(rolling_groups.with_unique_images) do |group|
|
|
16
|
+
Capistrano::ASG::Rolling::Instance.run(
|
|
17
|
+
autoscaling_group: group,
|
|
18
|
+
overrides: config.instance_overrides
|
|
19
|
+
).tap { |instance| config.instances << instance }
|
|
20
|
+
end
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
launched_instances.each do |instance|
|
|
14
23
|
logger.info "Launched Instance: **#{instance.id}**"
|
|
15
|
-
config.instances << instance
|
|
16
24
|
|
|
25
|
+
group = instance.autoscale_group
|
|
17
26
|
add_instance(instance, group.properties)
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
end
|
|
28
|
+
end
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
standard_groups = config.autoscale_groups.standard
|
|
31
|
+
standard_groups.each do |group|
|
|
32
|
+
logger.info "Auto Scaling Group: **#{group.name}**, standard deployment strategy."
|
|
33
|
+
|
|
34
|
+
group.instances.each_with_index do |instance, index|
|
|
35
|
+
server_properties =
|
|
22
36
|
if index.zero? && group.properties.key?(:primary_roles)
|
|
23
|
-
|
|
24
|
-
server_properties[:roles] = server_properties.delete(:primary_roles)
|
|
37
|
+
group.properties.merge(roles: group.properties[:primary_roles]).except(:primary_roles)
|
|
25
38
|
else
|
|
26
|
-
|
|
39
|
+
group.properties
|
|
27
40
|
end
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
end
|
|
42
|
+
add_instance(instance, server_properties)
|
|
31
43
|
end
|
|
32
44
|
end
|
|
45
|
+
end
|
|
33
46
|
|
|
47
|
+
desc 'Wait for SSH to become available on launched instances'
|
|
48
|
+
task :wait_for_ssh do
|
|
34
49
|
unless config.instances.empty?
|
|
35
50
|
logger.info 'Waiting for SSH to be available...'
|
|
36
51
|
|
|
@@ -54,31 +69,22 @@ namespace :rolling do
|
|
|
54
69
|
updated_templates = launch_templates.update(amis: amis, description: revision_log_message)
|
|
55
70
|
|
|
56
71
|
logger.info 'Triggering Instance Refresh on Auto Scaling Group(s)...'
|
|
57
|
-
updated_templates.
|
|
58
|
-
config.autoscale_groups.with_launch_template(launch_template).
|
|
59
|
-
|
|
72
|
+
groups_with_templates = updated_templates.flat_map do |launch_template|
|
|
73
|
+
config.autoscale_groups.with_launch_template(launch_template).map { |group| [group, launch_template] }
|
|
74
|
+
end
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
Capistrano::ASG::Rolling::Parallel.run(groups_with_templates) do |group, launch_template|
|
|
77
|
+
group.start_instance_refresh(launch_template)
|
|
78
|
+
|
|
79
|
+
logger.verbose "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
|
|
80
|
+
rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
|
|
81
|
+
logger.warning "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
|
|
65
82
|
end
|
|
66
83
|
|
|
67
84
|
config.launch_templates.merge(updated_templates)
|
|
68
85
|
end
|
|
69
86
|
end
|
|
70
87
|
|
|
71
|
-
desc 'Trigger instance refresh of deployed autoscaling groups'
|
|
72
|
-
task :trigger_instance_refresh do
|
|
73
|
-
logger.info 'Triggering Instance Refresh on Auto Scaling Group(s)...'
|
|
74
|
-
config.autoscale_groups.each do |group|
|
|
75
|
-
group.start_instance_refresh(group.launch_template)
|
|
76
|
-
logger.info "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
|
|
77
|
-
rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
|
|
78
|
-
logger.info "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
88
|
desc 'Clean up old Launch Template versions and AMIs and terminate instances'
|
|
83
89
|
task :cleanup do
|
|
84
90
|
unless config.launch_templates.empty?
|
|
@@ -114,7 +120,7 @@ namespace :rolling do
|
|
|
114
120
|
end
|
|
115
121
|
end
|
|
116
122
|
|
|
117
|
-
instances = config.instances.
|
|
123
|
+
instances = config.instances.auto_terminatable
|
|
118
124
|
if instances.any?
|
|
119
125
|
logger.info 'Terminating instance(s)...'
|
|
120
126
|
begin
|
|
@@ -176,33 +182,50 @@ namespace :rolling do
|
|
|
176
182
|
|
|
177
183
|
logger.info 'Updating Launch Template with the new AMI...'
|
|
178
184
|
launch_template = group.launch_template
|
|
179
|
-
launch_template.create_version(image_id: ami.id, description: revision_log_message)
|
|
185
|
+
new_template = launch_template.create_version(image_id: ami.id, description: revision_log_message)
|
|
180
186
|
|
|
181
|
-
config.launch_templates <<
|
|
187
|
+
config.launch_templates << new_template
|
|
182
188
|
else
|
|
183
189
|
logger.error 'Unable to create AMI. No instance with a valid state was found in the Auto Scaling Group.'
|
|
184
190
|
end
|
|
185
191
|
end
|
|
186
192
|
end
|
|
187
193
|
|
|
194
|
+
desc 'Trigger instance refresh on all Auto Scaling Groups'
|
|
195
|
+
task :trigger_instance_refresh do
|
|
196
|
+
logger.info 'Triggering Instance Refresh on Auto Scaling Group(s)...'
|
|
197
|
+
Capistrano::ASG::Rolling::Parallel.run(config.autoscale_groups) do |group|
|
|
198
|
+
group.start_instance_refresh(group.launch_template)
|
|
199
|
+
|
|
200
|
+
logger.info "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
|
|
201
|
+
rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
|
|
202
|
+
logger.warning "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
188
206
|
desc 'Get status of instance refresh'
|
|
189
207
|
task :instance_refresh_status do
|
|
190
208
|
if config.wait_for_instance_refresh?
|
|
191
|
-
groups = config.autoscale_groups.to_h { |group| [group.name, group] }
|
|
192
|
-
|
|
209
|
+
groups = config.autoscale_groups.select(&:refresh_id).to_h { |group| [group.name, group] }
|
|
210
|
+
completed_refreshes = []
|
|
193
211
|
|
|
194
212
|
while groups.any?
|
|
195
213
|
groups.each do |name, group|
|
|
196
214
|
refresh = group.latest_instance_refresh
|
|
197
215
|
if refresh.nil? || refresh.completed?
|
|
198
|
-
logger.info "Auto Scaling Group: **#{name}**, completed with status '#{refresh.status}'." if refresh
|
|
199
|
-
|
|
200
|
-
|
|
216
|
+
logger.info "Auto Scaling Group: **#{name}**, completed with status '#{refresh.status}'." if refresh&.completed?
|
|
217
|
+
|
|
218
|
+
groups.delete(name)
|
|
219
|
+
completed_refreshes.push(refresh) if refresh
|
|
220
|
+
elsif refresh.percentage_complete
|
|
201
221
|
logger.info "Auto Scaling Group: **#{name}**, #{refresh.percentage_complete}% completed, status '#{refresh.status}'."
|
|
202
222
|
else
|
|
203
223
|
logger.info "Auto Scaling Group: **#{name}**, status '#{refresh.status}'."
|
|
204
224
|
end
|
|
225
|
+
rescue Capistrano::ASG::Rolling::InstanceRefreshStatusError => e
|
|
226
|
+
logger.warning "Auto Scaling Group: **#{name}**, failed to fetch status: #{e.message} - retrying on next poll."
|
|
205
227
|
end
|
|
228
|
+
|
|
206
229
|
next if groups.empty?
|
|
207
230
|
|
|
208
231
|
wait_for = config.instance_refresh_polling_interval
|
|
@@ -210,7 +233,7 @@ namespace :rolling do
|
|
|
210
233
|
sleep wait_for
|
|
211
234
|
end
|
|
212
235
|
|
|
213
|
-
failed =
|
|
236
|
+
failed = completed_refreshes.any?(&:failed?)
|
|
214
237
|
raise Capistrano::ASG::Rolling::InstanceRefreshFailed if failed
|
|
215
238
|
end
|
|
216
239
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capistrano-asg-rolling
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kentaa
|
|
8
|
+
- iRaiser
|
|
8
9
|
autorequire:
|
|
9
10
|
bindir: exe
|
|
10
11
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
13
|
dependencies:
|
|
13
14
|
- !ruby/object:Gem::Dependency
|
|
14
15
|
name: bundler
|
|
@@ -52,6 +53,20 @@ dependencies:
|
|
|
52
53
|
- - "~>"
|
|
53
54
|
- !ruby/object:Gem::Version
|
|
54
55
|
version: '3.0'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: simplecov
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '1.0'
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '1.0'
|
|
55
70
|
- !ruby/object:Gem::Dependency
|
|
56
71
|
name: webmock
|
|
57
72
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -130,7 +145,7 @@ dependencies:
|
|
|
130
145
|
version: '1'
|
|
131
146
|
description:
|
|
132
147
|
email:
|
|
133
|
-
-
|
|
148
|
+
- tech-arnhem@iraiser.eu
|
|
134
149
|
executables: []
|
|
135
150
|
extensions: []
|
|
136
151
|
extra_rdoc_files: []
|
|
@@ -181,14 +196,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
181
196
|
requirements:
|
|
182
197
|
- - ">="
|
|
183
198
|
- !ruby/object:Gem::Version
|
|
184
|
-
version: 3.
|
|
199
|
+
version: 3.2.0
|
|
185
200
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
201
|
requirements:
|
|
187
202
|
- - ">="
|
|
188
203
|
- !ruby/object:Gem::Version
|
|
189
204
|
version: '0'
|
|
190
205
|
requirements: []
|
|
191
|
-
rubygems_version: 3.
|
|
206
|
+
rubygems_version: 3.4.19
|
|
192
207
|
signing_key:
|
|
193
208
|
specification_version: 4
|
|
194
209
|
summary: Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups
|