Skip to content

Commit b750af3

Browse files
authored
Merge pull request #7 from nginxinc/nginx-plus-sdk-update
Use new NGINX Plus API
2 parents 6b36bfc + 300cc70 commit b750af3

File tree

8 files changed

+585
-268
lines changed

8 files changed

+585
-268
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/build
2+
.DS_Store
23

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
GO_DOCKER_RUN = docker run --rm -v $(shell pwd)/cmd/sync:/go/src/github.com/nginxinc/nginx-asg-sync/cmd/sync -v $(shell pwd)/build:/build -w /go/src/github.com/nginxinc/nginx-asg-sync/cmd/sync
2-
GOLANG_CONTAINER = golang:1.8
2+
GOLANG_CONTAINER = golang:1.10
33

44
all: amazon centos7 ubuntu-trusty ubuntu-xenial
55

README.md

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
# NGINX Plus Integration with AWS Auto Scaling groups -- nginx-asg-sync
22

3-
**nginx-asg-sync** allows [NGINX Plus](https://www.nginx.com/products/) to support scaling when load balancing [AWS Auto Scaling groups](http://docs.aws.amazon.com/autoscaling/latest/userguide/WhatIsAutoScaling.html): when the number of instances in an Auto Scaling group changes, nginx-asg-sync adds the new instances to the NGINX Plus configuration and removes the terminated ones.
3+
**nginx-asg-sync** allows [NGINX Plus](https://www.nginx.com/products/) to discover instances of [AWS Auto Scaling groups](http://docs.aws.amazon.com/autoscaling/latest/userguide/WhatIsAutoScaling.html). When the number of instances in an Auto Scaling group changes, nginx-asg-sync adds the new instances to the NGINX Plus configuration and removes the terminated ones.
44

5-
More details on this solution are available in the blog post [Load Balancing AWS Auto Scaling Groups with NGINX Plus](https://www.nginx.com/blog/load-balancing-aws-auto-scaling-groups-nginx-plus/).
5+
## How It Works
6+
nginx-asg-sync must be installed on the same EC2 instance with NGINX Plus. nginx-asg-sync constantly monitors backend Auto Scaling groups via the AWS Auto Scaling API.
7+
When it sees that a scaling event has happened, it adds or removes the corresponding backend instances from the NGINX Plus configuration via the NGINX Plus API.
68

7-
Below you will find instructions on how to use nginx-asg-sync.
9+
**Note:** nginx-asg-sync does not scale Auto Scaling groups, it only gets the IP addresses of the instances of Auto Scaling groups.
810

9-
## Contents
11+
In the example below, NGINX Plus is configured to load balance among the instances of two Auto Scaling groups -- Backend One and Backend Two.
12+
nginx-asg-sync, running on the same instance as NGINX Plus, ensures that whenever you scale the Auto Scaling groups, the corresponding instances are added (or removed) from the NGINX Plus configuration.
1013

11-
1. [Supported Operating Systems](#supported-operating-systems)
12-
1. [Setting up Access to the AWS API](#setting-up-access-to-the-aws-api)
13-
1. [Installation](#installation)
14-
1. [Configuration](#configuration)
15-
1. [Usage](#usage)
16-
1. [Troubleshooting](#troubleshooting)
17-
1. [Building a Software Package](#building-a-software-package)
18-
1. [Support](#support)
14+
![nginx-asg-sync-architecture](https://cdn-1.wp.nginx.com/wp-content/uploads/2017/03/aws-auto-scaling-group-asg-sync.png)
15+
16+
Below you will find documentation on how to use nginx-asg-sync.
17+
18+
## Documentation
19+
**Note:** the documentation for **the latest stable release** is available via a link in the description of the release. See the [releases page](https://github.com/nginxinc/nginx-asg-sync/releases).
20+
21+
**Contents:**
22+
- [Supported Operating Systems](#supported-operating-systems)
23+
- [Setting up Access to the AWS API](#setting-up-access-to-the-aws-api)
24+
- [Installation](#installation)
25+
- [Configuration](#configuration)
26+
- [NGINX Plus Configuration](#nginx-plus-configuration)
27+
- [nginx-asg-sync Configuration](#nginx-asg-sync-configuration)
28+
- [Usage](#usage)
29+
- [Troubleshooting](#troubleshooting)
30+
- [Building a Software Package](#building-a-software-package)
31+
- [Support](#support)
1932

2033
## Supported Operating Systems
2134

@@ -34,18 +47,14 @@ nginx-asg-sync uses the AWS API to get the list of IP addresses of the instances
3447
1. [Create an IAM role](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) and attach the predefined `AmazonEC2ReadOnlyAccess` policy to it. This policy allows read-only access to EC2 APIs.
3548
1. When you launch the NGINX Plus instance, add this IAM role to the instance.
3649

37-
Alternatively, you can use the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environmental variables to provide credentials to nginx-asg-sync.
38-
3950
## Installation
4051

41-
To install nginx-asg-sync:
42-
43-
1. Download a software package for your OS with the latest version of nginx-asg-sync from the [Releases page](https://github.com/nginxinc/nginx-asg-sync/releases).
44-
1. Install the package:
45-
46-
For Amazon Linux or CentOS/RHEL, run: `$ sudo rpm -i <package-name>.rpm`
47-
48-
For Ubuntu, run: `$ sudo dpkg -i <package-name>.deb`
52+
1. Get a software package for your OS:
53+
* For a stable release, download a package from the [releases page](https://github.com/nginxinc/nginx-asg-sync/releases).
54+
* For the latest source code from the master branch, build a software package by following [these instructions](#building-a-software-package).
55+
2. Install the package:
56+
* For Amazon Linux or CentOS/RHEL, run: `$ sudo rpm -i <package-name>.rpm`
57+
* For Ubuntu, run: `$ sudo dpkg -i <package-name>.deb`
4958

5059
## Configuration
5160

@@ -54,6 +63,8 @@ As an example, we configure NGINX Plus to load balance two AWS Auto Scaling grou
5463
* Requests for /backend-one go to Backend One group.
5564
* Requests for /backend-two go to Backend Two group.
5665

66+
This example corresponds to [the diagram](#how-it-works) at the top of this README.
67+
5768
### NGINX Plus Configuration
5869

5970
```nginx
@@ -86,40 +97,28 @@ server {
8697
server {
8798
listen 8080;
8899
89-
root /usr/share/nginx/html;
90-
91-
location = / {
92-
return 302 /status.html;
93-
}
94-
95-
location = /status.html {
96-
}
97-
98-
location /status {
99-
access_log off;
100-
status;
100+
location /api {
101+
api write=on;
101102
}
102103
103-
location /upstream_conf {
104-
upstream_conf;
104+
location /dashboard.html {
105+
root /usr/share/nginx/html;
105106
}
106107
}
107108
```
108109

109110
* We declare two upstream groups – **backend-one** and **backend-two**, which correspond to our Auto Scaling groups. However, we do not add any servers to the upstream groups, because the servers will be added by nginx-aws-sync. The `state` directive names the file where the dynamically configurable list of servers is stored, enabling it to persist across restarts of NGINX Plus.
110111
* We define a virtual server that listens on port 80. NGINX Plus passes requests for **/backend-one** to the instances of the Backend One group, and requests for **/backend-two** to the instances of the Backend Two group.
111-
* We define a second virtual server listening on port 8080 and configure the NGINX Plus APIs on it, which are required by nginx-asg-sync:
112-
* The on-the-fly API is available at **127.0.0.1:8080/upstream_conf**
113-
* The status API is available at **127.0.0.1:8080/status**
112+
* We define a second virtual server listening on port 8080 and configure the NGINX Plus API on it, which is required by nginx-asg-sync:
113+
* The API is available at **127.0.0.1:8080/api**
114114

115115
### nginx-asg-sync Configuration
116116

117117
nginx-asg-sync is configured in the file **aws.yaml** in the **/etc/nginx** folder. For our example, we define the following configuration:
118118

119119
```yaml
120120
region: us-west-2
121-
upstream_conf_endpoint: http://127.0.0.1:8080/upstream_conf
122-
status_endpoint: http://127.0.0.1:8080/status
121+
api_endpoint: http://127.0.0.1:8080/api
123122
sync_interval_in_seconds: 5
124123
upstreams:
125124
- name: backend-one
@@ -133,13 +132,13 @@ upstreams:
133132
```
134133
135134
* The `region` key defines the AWS region where we deploy NGINX Plus and the Auto Scaling groups.
136-
* The `upstream_conf` and the `status_endpoint` keys define the NGINX Plus API endpoints.
135+
* The `api_endpoint` key defines the NGINX Plus API endpoint.
137136
* The `sync_interval_in_seconds` key defines the synchronization interval: nginx-asg-sync checks for scaling updates every 5 seconds.
138137
* The `upstreams` key defines the list of upstream groups. For each upstream group we specify:
139138
* `name` – The name we specified for the upstream block in the NGINX Plus configuration.
140139
* `autoscaling_group` – The name of the corresponding Auto Scaling group.
141140
* `port` – The port on which our backend applications are exposed.
142-
* `protocol` – The protocol of the traffic NGINX Plus load balances to the backend application, here `http`. If the application uses TCP/UDP, specify `stream` instead.
141+
* `kind` – The protocol of the traffic NGINX Plus load balances to the backend application, here `http`. If the application uses TCP/UDP, specify `stream` instead.
143142

144143
## Usage
145144

cmd/sync/config.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import (
99

1010
type config struct {
1111
Region string
12-
UpstreamConfEndpont string `yaml:"upstream_conf_endpoint"`
13-
StatusEndpoint string `yaml:"status_endpoint"`
12+
APIEndpoint string `yaml:"api_endpoint"`
1413
SyncIntervalInSeconds time.Duration `yaml:"sync_interval_in_seconds"`
1514
Upstreams []upstream
1615
}
@@ -58,11 +57,8 @@ func validateConfig(cfg *config) error {
5857
if cfg.Region == "" {
5958
return fmt.Errorf(errorMsgFormat, "region")
6059
}
61-
if cfg.UpstreamConfEndpont == "" {
62-
return fmt.Errorf(errorMsgFormat, "upstream_conf_endpoint")
63-
}
64-
if cfg.StatusEndpoint == "" {
65-
return fmt.Errorf(errorMsgFormat, "status_endpoint")
60+
if cfg.APIEndpoint == "" {
61+
return fmt.Errorf(errorMsgFormat, "api_endpoint")
6662
}
6763
if cfg.SyncIntervalInSeconds == 0 {
6864
return fmt.Errorf(intervalErrorMsg)

cmd/sync/config_test.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ package main
33
import "testing"
44

55
var validYaml = []byte(`region: us-west-2
6-
upstream_conf_endpoint: http://127.0.0.1:8080/upstream_conf
7-
status_endpoint: http://127.0.0.1:8080/status
6+
api_endpoint: http://127.0.0.1:8080/api
87
sync_interval_in_seconds: 5
98
upstreams:
109
- name: backend1
@@ -33,8 +32,7 @@ func getValidConfig() *config {
3332
}
3433
cfg := config{
3534
Region: "us-west-2",
36-
UpstreamConfEndpont: "http://127.0.0.1:8080/upstream_conf",
37-
StatusEndpoint: "http://127.0.0.1:8080/status",
35+
APIEndpoint: "http://127.0.0.1:8080/api",
3836
SyncIntervalInSeconds: 1,
3937
Upstreams: upstreams,
4038
}
@@ -49,13 +47,9 @@ func getInvalidConfigInput() []*testInput {
4947
invalidRegionCfg.Region = ""
5048
input = append(input, &testInput{invalidRegionCfg, "invalid region"})
5149

52-
invalidUpstreamConfEndponCfg := getValidConfig()
53-
invalidUpstreamConfEndponCfg.UpstreamConfEndpont = ""
54-
input = append(input, &testInput{invalidUpstreamConfEndponCfg, "invalid upstream_conf_endpoint"})
55-
56-
invalidStatusEndpointCfg := getValidConfig()
57-
invalidStatusEndpointCfg.StatusEndpoint = ""
58-
input = append(input, &testInput{invalidStatusEndpointCfg, "invalid status_endpoint"})
50+
invalidAPIEndpointCfg := getValidConfig()
51+
invalidAPIEndpointCfg.APIEndpoint = ""
52+
input = append(input, &testInput{invalidAPIEndpointCfg, "invalid api_endpoint"})
5953

6054
invalidSyncIntervalInSecondsCfg := getValidConfig()
6155
invalidSyncIntervalInSecondsCfg.SyncIntervalInSeconds = 0

cmd/sync/main.go

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ func main() {
5050
os.Exit(10)
5151
}
5252

53-
nginx, err := NewNginxClient(cfg.UpstreamConfEndpont, cfg.StatusEndpoint, connTimeoutInSecs*time.Second)
53+
httpClient := &http.Client{Timeout: connTimeoutInSecs * time.Second}
54+
nginx, err := NewNginxClient(httpClient, cfg.APIEndpoint)
55+
5456
if err != nil {
5557
log.Printf("Couldn't create NGINX client: %v", err)
5658
os.Exit(10)
@@ -60,10 +62,11 @@ func main() {
6062

6163
for _, ups := range cfg.Upstreams {
6264
if ups.Kind == "http" {
63-
err = nginx.CheckIfHTTPUpstreamExists(ups.Name)
65+
err = nginx.CheckIfUpstreamExists(ups.Name)
6466
} else {
6567
err = nginx.CheckIfStreamUpstreamExists(ups.Name)
6668
}
69+
6770
if err != nil {
6871
log.Printf("Problem with the NGINX configuration: %v", err)
6972
os.Exit(10)
@@ -81,33 +84,53 @@ func main() {
8184
signal.Notify(sigterm, syscall.SIGTERM)
8285

8386
for {
84-
for _, ups := range cfg.Upstreams {
85-
ips, err := awsClient.GetPrivateIPsOfInstancesOfAutoscalingGroup(ups.AutoscalingGroup)
87+
for _, upstream := range cfg.Upstreams {
88+
ips, err := awsClient.GetPrivateIPsOfInstancesOfAutoscalingGroup(upstream.AutoscalingGroup)
8689
if err != nil {
87-
log.Printf("Couldn't get the IP addresses of instances of the Auto Scaling group %v: %v", ups.AutoscalingGroup, err)
90+
log.Printf("Couldn't get the IP addresses of instances of the Auto Scaling group %v: %v", upstream.AutoscalingGroup, err)
8891
continue
8992
}
9093

91-
var backends []string
92-
for _, ip := range ips {
93-
backend := fmt.Sprintf("%v:%v", ip, ups.Port)
94-
backends = append(backends, backend)
95-
}
96-
97-
var added, removed []string
98-
99-
if ups.Kind == "http" {
100-
added, removed, err = nginx.UpdateHTTPServers(ups.Name, backends)
94+
if upstream.Kind == "http" {
95+
var upsServers []UpstreamServer
96+
for _, ip := range ips {
97+
backend := fmt.Sprintf("%v:%v", ip, upstream.Port)
98+
upsServers = append(upsServers, UpstreamServer{
99+
Server: backend,
100+
MaxFails: 1,
101+
})
102+
}
103+
104+
added, removed, err := nginx.UpdateHTTPServers(upstream.Name, upsServers)
105+
if err != nil {
106+
log.Printf("Couldn't update HTTP servers in NGINX: %v", err)
107+
continue
108+
}
109+
110+
if len(added) > 0 || len(removed) > 0 {
111+
log.Printf("Updated HTTP servers of %v; Added: %v, Removed: %v", upstream, added, removed)
112+
}
101113
} else {
102-
added, removed, err = nginx.UpdateStreamServers(ups.Name, backends)
103-
}
104-
if err != nil {
105-
log.Printf("Couldn't update servers in NGINX: %v", err)
106-
continue
107-
}
108-
if len(removed) > 0 || len(added) > 0 {
109-
log.Printf("Upstream: %v has been updated; Added: %v; Removed: %v\n", ups.Name, added, removed)
114+
var upsServers []StreamUpstreamServer
115+
for _, ip := range ips {
116+
backend := fmt.Sprintf("%v:%v", ip, upstream.Port)
117+
upsServers = append(upsServers, StreamUpstreamServer{
118+
Server: backend,
119+
MaxFails: 1,
120+
})
121+
}
122+
123+
added, removed, err := nginx.UpdateStreamServers(upstream.Name, upsServers)
124+
if err != nil {
125+
log.Printf("Couldn't update Steam servers in NGINX: %v", err)
126+
continue
127+
}
128+
129+
if len(added) > 0 || len(removed) > 0 {
130+
log.Printf("Updated Stream servers of %v; Added: %v, Removed: %v", upstream, added, removed)
131+
}
110132
}
133+
111134
}
112135

113136
select {

0 commit comments

Comments
 (0)