Skip to content

Commit

Permalink
Merge pull request #7 from nginxinc/nginx-plus-sdk-update
Browse files Browse the repository at this point in the history
Use new NGINX Plus API
  • Loading branch information
pleshakov committed Jun 26, 2018
2 parents 6b36bfc + 300cc70 commit b750af3
Show file tree
Hide file tree
Showing 8 changed files with 585 additions and 268 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/build
.DS_Store

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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
GOLANG_CONTAINER = golang:1.8
GOLANG_CONTAINER = golang:1.10

all: amazon centos7 ubuntu-trusty ubuntu-xenial

Expand Down
85 changes: 42 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
# NGINX Plus Integration with AWS Auto Scaling groups -- nginx-asg-sync

**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.
**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.

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/).
## How It Works
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.
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.

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

## Contents
In the example below, NGINX Plus is configured to load balance among the instances of two Auto Scaling groups -- Backend One and Backend Two.
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.

1. [Supported Operating Systems](#supported-operating-systems)
1. [Setting up Access to the AWS API](#setting-up-access-to-the-aws-api)
1. [Installation](#installation)
1. [Configuration](#configuration)
1. [Usage](#usage)
1. [Troubleshooting](#troubleshooting)
1. [Building a Software Package](#building-a-software-package)
1. [Support](#support)
![nginx-asg-sync-architecture](https://cdn-1.wp.nginx.com/wp-content/uploads/2017/03/aws-auto-scaling-group-asg-sync.png)

Below you will find documentation on how to use nginx-asg-sync.

## Documentation
**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).

**Contents:**
- [Supported Operating Systems](#supported-operating-systems)
- [Setting up Access to the AWS API](#setting-up-access-to-the-aws-api)
- [Installation](#installation)
- [Configuration](#configuration)
- [NGINX Plus Configuration](#nginx-plus-configuration)
- [nginx-asg-sync Configuration](#nginx-asg-sync-configuration)
- [Usage](#usage)
- [Troubleshooting](#troubleshooting)
- [Building a Software Package](#building-a-software-package)
- [Support](#support)

## Supported Operating Systems

Expand All @@ -34,18 +47,14 @@ nginx-asg-sync uses the AWS API to get the list of IP addresses of the instances
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.
1. When you launch the NGINX Plus instance, add this IAM role to the instance.

Alternatively, you can use the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environmental variables to provide credentials to nginx-asg-sync.

## Installation

To install nginx-asg-sync:

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).
1. Install the package:

For Amazon Linux or CentOS/RHEL, run: `$ sudo rpm -i <package-name>.rpm`

For Ubuntu, run: `$ sudo dpkg -i <package-name>.deb`
1. Get a software package for your OS:
* For a stable release, download a package from the [releases page](https://github.com/nginxinc/nginx-asg-sync/releases).
* For the latest source code from the master branch, build a software package by following [these instructions](#building-a-software-package).
2. Install the package:
* For Amazon Linux or CentOS/RHEL, run: `$ sudo rpm -i <package-name>.rpm`
* For Ubuntu, run: `$ sudo dpkg -i <package-name>.deb`

## Configuration

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

This example corresponds to [the diagram](#how-it-works) at the top of this README.

### NGINX Plus Configuration

```nginx
Expand Down Expand Up @@ -86,40 +97,28 @@ server {
server {
listen 8080;
root /usr/share/nginx/html;
location = / {
return 302 /status.html;
}
location = /status.html {
}
location /status {
access_log off;
status;
location /api {
api write=on;
}
location /upstream_conf {
upstream_conf;
location /dashboard.html {
root /usr/share/nginx/html;
}
}
```

* 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.
* 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.
* 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:
* The on-the-fly API is available at **127.0.0.1:8080/upstream_conf**
* The status API is available at **127.0.0.1:8080/status**
* 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:
* The API is available at **127.0.0.1:8080/api**

### nginx-asg-sync Configuration

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

```yaml
region: us-west-2
upstream_conf_endpoint: http://127.0.0.1:8080/upstream_conf
status_endpoint: http://127.0.0.1:8080/status
api_endpoint: http://127.0.0.1:8080/api
sync_interval_in_seconds: 5
upstreams:
- name: backend-one
Expand All @@ -133,13 +132,13 @@ upstreams:
```
* The `region` key defines the AWS region where we deploy NGINX Plus and the Auto Scaling groups.
* The `upstream_conf` and the `status_endpoint` keys define the NGINX Plus API endpoints.
* The `api_endpoint` key defines the NGINX Plus API endpoint.
* The `sync_interval_in_seconds` key defines the synchronization interval: nginx-asg-sync checks for scaling updates every 5 seconds.
* The `upstreams` key defines the list of upstream groups. For each upstream group we specify:
* `name` – The name we specified for the upstream block in the NGINX Plus configuration.
* `autoscaling_group` – The name of the corresponding Auto Scaling group.
* `port` – The port on which our backend applications are exposed.
* `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.
* `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.

## Usage

Expand Down
10 changes: 3 additions & 7 deletions cmd/sync/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (

type config struct {
Region string
UpstreamConfEndpont string `yaml:"upstream_conf_endpoint"`
StatusEndpoint string `yaml:"status_endpoint"`
APIEndpoint string `yaml:"api_endpoint"`
SyncIntervalInSeconds time.Duration `yaml:"sync_interval_in_seconds"`
Upstreams []upstream
}
Expand Down Expand Up @@ -58,11 +57,8 @@ func validateConfig(cfg *config) error {
if cfg.Region == "" {
return fmt.Errorf(errorMsgFormat, "region")
}
if cfg.UpstreamConfEndpont == "" {
return fmt.Errorf(errorMsgFormat, "upstream_conf_endpoint")
}
if cfg.StatusEndpoint == "" {
return fmt.Errorf(errorMsgFormat, "status_endpoint")
if cfg.APIEndpoint == "" {
return fmt.Errorf(errorMsgFormat, "api_endpoint")
}
if cfg.SyncIntervalInSeconds == 0 {
return fmt.Errorf(intervalErrorMsg)
Expand Down
16 changes: 5 additions & 11 deletions cmd/sync/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package main
import "testing"

var validYaml = []byte(`region: us-west-2
upstream_conf_endpoint: http://127.0.0.1:8080/upstream_conf
status_endpoint: http://127.0.0.1:8080/status
api_endpoint: http://127.0.0.1:8080/api
sync_interval_in_seconds: 5
upstreams:
- name: backend1
Expand Down Expand Up @@ -33,8 +32,7 @@ func getValidConfig() *config {
}
cfg := config{
Region: "us-west-2",
UpstreamConfEndpont: "http://127.0.0.1:8080/upstream_conf",
StatusEndpoint: "http://127.0.0.1:8080/status",
APIEndpoint: "http://127.0.0.1:8080/api",
SyncIntervalInSeconds: 1,
Upstreams: upstreams,
}
Expand All @@ -49,13 +47,9 @@ func getInvalidConfigInput() []*testInput {
invalidRegionCfg.Region = ""
input = append(input, &testInput{invalidRegionCfg, "invalid region"})

invalidUpstreamConfEndponCfg := getValidConfig()
invalidUpstreamConfEndponCfg.UpstreamConfEndpont = ""
input = append(input, &testInput{invalidUpstreamConfEndponCfg, "invalid upstream_conf_endpoint"})

invalidStatusEndpointCfg := getValidConfig()
invalidStatusEndpointCfg.StatusEndpoint = ""
input = append(input, &testInput{invalidStatusEndpointCfg, "invalid status_endpoint"})
invalidAPIEndpointCfg := getValidConfig()
invalidAPIEndpointCfg.APIEndpoint = ""
input = append(input, &testInput{invalidAPIEndpointCfg, "invalid api_endpoint"})

invalidSyncIntervalInSecondsCfg := getValidConfig()
invalidSyncIntervalInSecondsCfg.SyncIntervalInSeconds = 0
Expand Down
69 changes: 46 additions & 23 deletions cmd/sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ func main() {
os.Exit(10)
}

nginx, err := NewNginxClient(cfg.UpstreamConfEndpont, cfg.StatusEndpoint, connTimeoutInSecs*time.Second)
httpClient := &http.Client{Timeout: connTimeoutInSecs * time.Second}
nginx, err := NewNginxClient(httpClient, cfg.APIEndpoint)

if err != nil {
log.Printf("Couldn't create NGINX client: %v", err)
os.Exit(10)
Expand All @@ -60,10 +62,11 @@ func main() {

for _, ups := range cfg.Upstreams {
if ups.Kind == "http" {
err = nginx.CheckIfHTTPUpstreamExists(ups.Name)
err = nginx.CheckIfUpstreamExists(ups.Name)
} else {
err = nginx.CheckIfStreamUpstreamExists(ups.Name)
}

if err != nil {
log.Printf("Problem with the NGINX configuration: %v", err)
os.Exit(10)
Expand All @@ -81,33 +84,53 @@ func main() {
signal.Notify(sigterm, syscall.SIGTERM)

for {
for _, ups := range cfg.Upstreams {
ips, err := awsClient.GetPrivateIPsOfInstancesOfAutoscalingGroup(ups.AutoscalingGroup)
for _, upstream := range cfg.Upstreams {
ips, err := awsClient.GetPrivateIPsOfInstancesOfAutoscalingGroup(upstream.AutoscalingGroup)
if err != nil {
log.Printf("Couldn't get the IP addresses of instances of the Auto Scaling group %v: %v", ups.AutoscalingGroup, err)
log.Printf("Couldn't get the IP addresses of instances of the Auto Scaling group %v: %v", upstream.AutoscalingGroup, err)
continue
}

var backends []string
for _, ip := range ips {
backend := fmt.Sprintf("%v:%v", ip, ups.Port)
backends = append(backends, backend)
}

var added, removed []string

if ups.Kind == "http" {
added, removed, err = nginx.UpdateHTTPServers(ups.Name, backends)
if upstream.Kind == "http" {
var upsServers []UpstreamServer
for _, ip := range ips {
backend := fmt.Sprintf("%v:%v", ip, upstream.Port)
upsServers = append(upsServers, UpstreamServer{
Server: backend,
MaxFails: 1,
})
}

added, removed, err := nginx.UpdateHTTPServers(upstream.Name, upsServers)
if err != nil {
log.Printf("Couldn't update HTTP servers in NGINX: %v", err)
continue
}

if len(added) > 0 || len(removed) > 0 {
log.Printf("Updated HTTP servers of %v; Added: %v, Removed: %v", upstream, added, removed)
}
} else {
added, removed, err = nginx.UpdateStreamServers(ups.Name, backends)
}
if err != nil {
log.Printf("Couldn't update servers in NGINX: %v", err)
continue
}
if len(removed) > 0 || len(added) > 0 {
log.Printf("Upstream: %v has been updated; Added: %v; Removed: %v\n", ups.Name, added, removed)
var upsServers []StreamUpstreamServer
for _, ip := range ips {
backend := fmt.Sprintf("%v:%v", ip, upstream.Port)
upsServers = append(upsServers, StreamUpstreamServer{
Server: backend,
MaxFails: 1,
})
}

added, removed, err := nginx.UpdateStreamServers(upstream.Name, upsServers)
if err != nil {
log.Printf("Couldn't update Steam servers in NGINX: %v", err)
continue
}

if len(added) > 0 || len(removed) > 0 {
log.Printf("Updated Stream servers of %v; Added: %v, Removed: %v", upstream, added, removed)
}
}

}

select {
Expand Down
Loading

0 comments on commit b750af3

Please sign in to comment.