Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding DNS automation #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions dns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AWS Route53

## What does this program do?
It detects the number of tainted nodes on the cluster on which Nginx is deployed , calculates weight such that traffic is equally distributed to all the records created and creates the `A` record with desired subdomain name.

## Steps to be followed
- Nodes are tainted with label `proxy` so that only nginx pods are scheduled on these nodes. For example:

`kubectl taint node ip-192-168-7-148.ap-south-1.compute.internal proxy=:NoExecute `

- Deploy Fission

- Deploy Nginx along with configuration which can resolve to Fission Router.

`kubectl apply -f nginx/`

- Create a hosted zone in Route53

- Run the program with following parameters:

- Target: Subdomain to be created in the hosted zone
- ZoneID : The zone ID of the hosted
- TTL(optional) : cache time to live in seconds :

`go run main.go -t test1.infracloud.club -z Z04819012F7MC0LH31Q6R`


14 changes: 14 additions & 0 deletions dns/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module dns

go 1.14

require (
github.com/aws/aws-sdk-go v1.36.18
github.com/google/go-cmp v0.4.0 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/satori/go.uuid v1.2.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
k8s.io/api v0.17.3
k8s.io/apimachinery v0.17.3
k8s.io/client-go v0.17.3
)
224 changes: 224 additions & 0 deletions dns/go.sum

Large diffs are not rendered by default.

202 changes: 202 additions & 0 deletions dns/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
Copyright 2020 Fission Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

//Assumptions for this script to work:
// 1. Nodes are tainted with "proxy" as key
// 2. Nginx is deployed with service name of "nginx" as NodePort service

package main

import (
"flag"
"fmt"
"path/filepath"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
uuid "github.com/satori/go.uuid"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)

// var name string
var target string
var TTL int64
var weight = int64(1)
var zoneID string

func init() {
// flag.StringVar(&name, "d", "", "domain name")
flag.StringVar(&target, "t", "", "target of domain name. The subdomain with which weighted record is to be created")
flag.StringVar(&zoneID, "z", "", "AWS Zone Id for domain")
flag.Int64Var(&TTL, "ttl", int64(60), "TTL for DNS Cache")
}

func main() {
flag.Parse()
if target == "" || zoneID == "" {
fmt.Println(fmt.Errorf("incomplete arguments: t: %s, z: %s", target, zoneID))
flag.PrintDefaults()
return
}
sess, err := session.NewSession()
if err != nil {
fmt.Println("failed to create session,", err)
return
}

svc := route53.New(sess)

var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()

// use the current context in kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err.Error())
}

// create the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}

externalIP := FindExternalIP(clientset)
// fmt.Println(externalIP)

nodeport := FindNginxSvc(clientset)
// fmt.Println(nodeport)

weight := FindWeight(len(externalIP))
// fmt.Println(weight)

CreateRecords(svc, weight, externalIP)
endpoint := target + ":" + fmt.Sprint(nodeport)
fmt.Printf("Service is reachable at %s \n", endpoint)
}

// CreateRecords creates weighted records
func CreateRecords(svc *route53.Route53, weight int, externalIP []string) {
var cb *route53.ChangeBatch
var changes []*route53.Change
var rrslice []*route53.ResourceRecord
var rrs *route53.ResourceRecordSet
var rr route53.ResourceRecord

createAction := route53.ChangeActionCreate
weight64 := int64(weight)
recordType := route53.RRTypeA

for _, ip := range externalIP {

rr = route53.ResourceRecord{Value: &ip}
rrslice = append(rrslice, &rr)
uuid := uuid.NewV4().String()
rrs = &route53.ResourceRecordSet{
Name: &target,
TTL: &TTL,
ResourceRecords: rrslice,
Type: &recordType,
Weight: &weight64,
SetIdentifier: &uuid,
}

change := route53.Change{
Action: &createAction,
ResourceRecordSet: rrs,
}
changes = append(changes, &change)

cb = &route53.ChangeBatch{Changes: changes}
var crsi *route53.ChangeResourceRecordSetsInput
crsi = &route53.ChangeResourceRecordSetsInput{
HostedZoneId: &zoneID,
ChangeBatch: cb,
}
resp, err := svc.ChangeResourceRecordSets(crsi)
if err != nil {
fmt.Println(err)
}
changes = []*route53.Change{}
rrslice = []*route53.ResourceRecord{}

fmt.Println(resp)

}
}

// FindNginxSvc finds the NginxSvc and returns the port which it is running on
func FindNginxSvc(clientset *kubernetes.Clientset) int32 {
var nodeport int32
svcs, err := clientset.CoreV1().Services(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
panic(fmt.Errorf("Error fetching services %v ", err))
}
for _, svc := range svcs.Items {
if svc.Name == "nginx" {
nodeport = svc.Spec.Ports[0].NodePort
}
}
return nodeport
}

// FindExternalIP gives the external IPs of nodes which are tainted
func FindExternalIP(clientset *kubernetes.Clientset) []string {
taint := v1.Taint{
Key: "proxy",
}

var nodeAddresses []v1.NodeAddress
var externalIP []string
nodes, err := clientset.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
panic(fmt.Errorf("Error fetching nodes %v ", err))
}
taintedNodes := make([]v1.Node, 0)
for _, node := range nodes.Items {

for _, t := range node.Spec.Taints {
if t.Key == taint.Key {
taintedNodes = append(taintedNodes, node)
// 2nd element holds the external ip
nodeAddresses = append(nodeAddresses, node.Status.Addresses[1])
}
}

}
for _, address := range nodeAddresses {
externalIP = append(externalIP, address.Address)
}
return externalIP
}

// FindWeight calculates equally distributed weight for each record based on number of nodes nginx is running on
// Ref: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html
func FindWeight(numOfRecords int) int {
const max = 255
weight := max / numOfRecords
return weight
}
42 changes: 42 additions & 0 deletions dns/nginx/cm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: confnginx
data:
nginx.conf: |
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 2000000;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
keepalive_requests 150000;
sendfile on;
server {
listen 80;

resolver kube-dns.kube-system.svc.cluster.local valid=5s;

location /healthz {
return 200;
}

location / {
proxy_connect_timeout 10000;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_send_timeout 10000;
proxy_read_timeout 10000;
send_timeout 100000;
proxy_pass http://router.fission.svc.cluster.local;
}
}
}
42 changes: 42 additions & 0 deletions dns/nginx/nginx.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: "kubernetes.io/hostname"
tolerations:
- key: "proxy"
operator: "Exists"
effect: "NoExecute"
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: confnginx

13 changes: 13 additions & 0 deletions dns/nginx/svc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
externalTrafficPolicy: Cluster
selector:
app: nginx
type: NodePort
ports:
- name: http
port: 80