Created
April 17, 2015 01:41
-
-
Save sawanoboly/fe4e17f6991bb034ade2 to your computer and use it in GitHub Desktop.
GolangでHTTPのヘルスチェックをベースにDNSのレコードを更新してみる ref: http://qiita.com/sawanoboly/items/7cf7bcc956b28a7de14e
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.envrc | |
layout go | |
export DNSIMPLE_TOKEN='*********************' | |
export DNSIMPLE_USER='*********************@higanworks.com' | |
export DNS_TARGET_DOMAIN='example.net' | |
export DNS_TARGET_RR='wwwww' | |
export DNS_TARGET_HOSTS='210.152.xxx.xx1,210.152.xxx.xx2' | |
export DNS_BACKUP_HOSTS='210.152.xxx.xx3' | |
export AWS_ACCESS_KEY='*********************' | |
export AWS_SECRET_ACCESS_KEY='*********************' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gox -osarch="linux/amd64 darwin/amd64" --output="build/{{.OS}}/{{.Arch}}/healthbased-dnsrr" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
dnsimple "github.com/rubyist/go-dnsimple" | |
"os" | |
) | |
type DnsimpleManage struct { | |
client *dnsimple.DNSimpleClient | |
TargetHosts []string | |
BackupHosts []string | |
TargetRR string | |
targetDomainName string | |
targetDomainId int | |
countAlivedTarget int | |
apiToken string | |
email string | |
} | |
func (dm *DnsimpleManage) RecordExists(records []dnsimple.Record, host string) int { | |
for _, val := range records { | |
if val.Name != dm.TargetRR { | |
continue | |
} | |
if val.Content == host { | |
fmt.Printf("Record: %s -> %s\n", val.Name, val.Content) | |
return val.Id | |
} | |
} | |
return 0 | |
} | |
func (dm *DnsimpleManage) getDomainId() int { | |
domains, _ := dm.client.Domains() | |
for _, domain := range domains { | |
if domain.Name == dm.targetDomainName { | |
fmt.Printf("Domain: %s %d\n", domain.Name, domain.Id) | |
return domain.Id | |
} | |
} | |
return 0 | |
} | |
func (dm *DnsimpleManage) healthBased_rr() int { | |
dm.countAlivedTarget = len(dm.TargetHosts) | |
dm.client = dnsimple.NewClient(dm.apiToken, dm.email) | |
// Get a list of your domains | |
dm.targetDomainId = dm.getDomainId() | |
if dm.targetDomainId == 0 { | |
fmt.Printf("Exit: Target Domain Not found.\n") | |
os.Exit(1) | |
} | |
// Get a list of records for a domain | |
records, _ := dm.client.Records(dm.targetDomainName, "", "") | |
for _, val := range dm.TargetHosts { | |
r_id := dm.RecordExists(records, val) | |
status, err := tryHttp(val) | |
if err != nil { | |
if r_id != 0 { | |
fmt.Printf("Dead But Exists!: I'll delete [ %d ]. \n", r_id) | |
delRec := dnsimple.Record{Id: r_id, DomainId: dm.targetDomainId} | |
err := delRec.Delete(dm.client) | |
if err != nil { | |
fmt.Printf("Delete returned error: %v\n", err) | |
} | |
} else { | |
fmt.Printf("Result: Dead\n") | |
} | |
dm.countAlivedTarget-- | |
continue | |
} | |
fmt.Printf("Result: %d\n", status) | |
// Create a new Record | |
// go func() { | |
if r_id == 0 { | |
newRec := dnsimple.Record{Name: dm.TargetRR, Content: val, RecordType: "A", TTL: 600} | |
rec, _ := dm.client.CreateRecord(dm.targetDomainName, newRec) | |
fmt.Printf("RecordID: %d\n", rec.Id) | |
} else { | |
fmt.Printf("Record Already Exists\n") | |
} | |
// }() | |
} | |
return dm.countAlivedTarget | |
} | |
func (dm *DnsimpleManage) enableBackup() int { | |
return 0 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ ./build/darwin/amd64/healthbased-dnsrr dnsimple | |
Domain: example.net 119xxx | |
Record: wwwww -> 210.152.xxx.xx1 | |
Dead But Exists!: I'll delete [ 39xxxxx ]. | |
Record: wwwww -> 210.152.xxx.xx2 | |
Result: 200 | |
Record Already Exists |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ ./build/darwin/amd64/healthbased-dnsrr route53 | |
Found: /hostedzone/Z1KM1PIxxxxxxx: example.net. | |
Finding: wwwww.example.net. | |
Current RR record: ["210.152.xxx.xx2"] | |
Check HTTP: 210.152.xxx.xx1 200 | |
Check HTTP: 210.152.xxx.xx2 200 | |
Avaliable targets: ["210.152.xxx.xx1" "210.152.xxx.xx2"] | |
Found difference, I'll fix it. | |
Change Status: PENDING |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ tree build | |
build | |
├── darwin | |
│ └── amd64 | |
│ └── healthbased-dnsrr | |
└── linux | |
└── amd64 | |
└── healthbased-dnsrr | |
4 directories, 2 files |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"net/http" | |
"time" | |
) | |
var httpClient = &http.Client{Timeout: time.Duration(10) * time.Second} | |
func tryHttp(host string) (int, error) { | |
resp, err := httpClient.Get("http://" + host + "/") | |
if err != nil { | |
return 0, err | |
} | |
defer resp.Body.Close() | |
return resp.StatusCode, err | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
envmap "github.com/higanworks/go-envmap" | |
"os" | |
"strings" | |
) | |
var envs = envmap.All() | |
var targetHosts = strings.Split(envs["DNS_TARGET_HOSTS"], ",") | |
var backupHosts = strings.Split(envs["DNS_BACKUP_HOSTS"], ",") | |
type DnsManage interface { | |
healthBased_rr() int | |
// enableBackup() error | |
} | |
func main() { | |
os.Exit(realMain()) | |
} | |
func realMain() int { | |
var manager DnsManage | |
if len(os.Args) < 2 { | |
fmt.Printf("Usage: healthbased-dnsrr [dnsimple|route53]\n") | |
return 1 | |
} | |
switch os.Args[1] { | |
default: | |
fmt.Printf("Usage: healthbased-dnsrr [dnsimple|route53]\n") | |
return 1 | |
case "dnsimple": | |
manager = &DnsimpleManage{ | |
TargetHosts: targetHosts, | |
BackupHosts: backupHosts, | |
TargetRR: envs["DNS_TARGET_RR"], | |
targetDomainName: envs["DNS_TARGET_DOMAIN"], | |
apiToken: envs["DNSIMPLE_TOKEN"], | |
email: envs["DNSIMPLE_USER"], | |
} | |
case "route53": | |
manager = &Route53Manage{ | |
TargetHosts: targetHosts, | |
BackupHosts: backupHosts, | |
TargetRR: envs["DNS_TARGET_RR"], | |
targetDomainName: envs["DNS_TARGET_DOMAIN"], | |
} | |
} | |
life := manager.healthBased_rr() | |
if life == 0 { | |
fmt.Printf("All Dead\n") | |
// manager.enableBackup() | |
} | |
return 0 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"github.com/higanworks/goamz/route53" | |
"github.com/mitchellh/goamz/aws" | |
"reflect" | |
"sort" | |
"strings" | |
) | |
type Route53Manage struct { | |
client *route53.Route53 | |
TargetHosts []string | |
currentRRHosts []string | |
updateRRHosts []string | |
BackupHosts []string | |
TargetRR string | |
targetDomainName string | |
targetDomainId string | |
countAlivedTarget int | |
} | |
func (dm *Route53Manage) extractDomainId(res_id string) string { | |
id := strings.Split(res_id, "/") | |
return id[2] | |
} | |
func (dm *Route53Manage) RecordExists(records []route53.ResourceRecordSet) route53.ResourceRecordSet { | |
fmt.Printf("Finding: " + dm.TargetRR + "." + dm.targetDomainName + ".\n") | |
for _, val := range records { | |
if val.Name == dm.TargetRR+"."+dm.targetDomainName+"." { | |
return val | |
} else if val.Name == dm.TargetRR+"."+dm.targetDomainName { | |
return val | |
} | |
} | |
return route53.ResourceRecordSet{} | |
} | |
func (dm *Route53Manage) getDomainId() string { | |
res, _ := dm.client.ListHostedZonesByName(dm.targetDomainName, "", 1) | |
for _, zone := range res.HostedZones { | |
if zone.Name == dm.targetDomainName+"." { | |
fmt.Printf("Found: %s: %s\n", zone.ID, zone.Name) | |
return dm.extractDomainId(zone.ID) | |
} else if zone.Name == dm.targetDomainName { | |
fmt.Printf("Found: %s: %s\n", zone.ID, zone.Name) | |
return dm.extractDomainId(zone.ID) | |
} | |
} | |
return "0" | |
} | |
func (dm *Route53Manage) healthBased_rr() int { | |
dm.countAlivedTarget = len(dm.TargetHosts) | |
auth, _ := aws.EnvAuth() // AWS_ACCESS_KEY, AWS_SECRET_ACCESS_KEY | |
dm.client = route53.New(auth, aws.USEast) | |
// Get a list of your domains | |
dm.targetDomainId = dm.getDomainId() | |
res, err := dm.client.ListResourceRecordSets(dm.targetDomainId, nil) | |
if err != nil { | |
panic(err) | |
} | |
rrr := dm.RecordExists(res.Records) | |
if rrr.Name != "" { | |
dm.currentRRHosts = rrr.Records | |
fmt.Printf("Current RR record: %q\n", dm.currentRRHosts) | |
} else { | |
fmt.Printf("Current RR record: Nothing\n") | |
} | |
for _, host := range dm.TargetHosts { | |
status, _ := tryHttp(host) | |
if err != nil { | |
status = 0 | |
} | |
fmt.Printf("Check HTTP: %s %d\n", host, status) | |
if status != 200 { | |
dm.countAlivedTarget-- | |
continue | |
} | |
dm.updateRRHosts = append(dm.updateRRHosts, host) | |
} | |
sort.StringSlice(dm.updateRRHosts).Sort() | |
sort.StringSlice(dm.currentRRHosts).Sort() | |
sort.StringSlice(dm.BackupHosts).Sort() | |
fmt.Printf("Avaliable targets: %q\n", dm.updateRRHosts) | |
if !reflect.DeepEqual(dm.updateRRHosts, dm.currentRRHosts) { | |
var req route53.ChangeResourceRecordSetsRequest | |
var req_rr route53.ResourceRecordSet | |
var change route53.Change | |
if dm.countAlivedTarget != 0 { | |
fmt.Printf("Found difference, I'll fix it.\n") | |
req_rr.Name = dm.TargetRR + "." + dm.targetDomainName + "." | |
req_rr.Type = "A" | |
req_rr.TTL = 600 | |
req_rr.Records = dm.updateRRHosts | |
change.Action = "UPSERT" | |
change.Record = req_rr | |
req.Comment = change.Action + " to " + dm.TargetRR + "." + dm.targetDomainName + "." | |
req.Changes = append(req.Changes, change) | |
res, err2 := dm.client.ChangeResourceRecordSets(dm.targetDomainId, &req) | |
if err2 != nil { | |
panic(err) | |
} | |
fmt.Printf("Change Status: %s\n", res.ChangeInfo.Status) | |
} else { | |
// Delete if BackupHosts Empty | |
if dm.targetDomainId != "0" { | |
fmt.Printf("All Hosts dead. I'll delete rrset.\n") | |
req_rr.Name = dm.TargetRR + "." + dm.targetDomainName + "." | |
req_rr.Type = "A" | |
req_rr.TTL = 600 | |
req_rr.Records = dm.currentRRHosts | |
change.Action = "DELETE" | |
change.Record = req_rr | |
req.Comment = change.Action + " to " + dm.TargetRR + "." + dm.targetDomainName + "." | |
req.Changes = append(req.Changes, change) | |
res, err2 := dm.client.ChangeResourceRecordSets(dm.targetDomainId, &req) | |
if err2 != nil { | |
panic(err2) | |
} | |
fmt.Printf("Change Status: %s\n", res.ChangeInfo.Status) | |
} | |
} | |
} | |
return dm.countAlivedTarget | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment