Commit bd43dbbd authored by Geoff Simmons's avatar Geoff Simmons

Support ExternalName Services as IngressBackends (more to come).

Uses VMOD dynamic, and requires that the getdns library is installed
in the image running Varnish. This allows us to use dynamic.resolve(),
in particular so that TTLs from DNS are honored.

Currently sets ttl to a hard-wired value of 30s. Since the TTLs for
lookup are obtained from DNS, this actually sets the delay until
lookups are retried after negative results (default 1h).

The next step is to test and extend BackendConfig support to configure
properties of VMOD dynamic. That will make it possible to configure
the ttl value (although we might stay with a much shorter ttl than
1h).

Partially addresses gitlab issue #20.
parent 67bea72d
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress-varnish
annotations:
kubernetes.io/ingress.class: "varnish"
spec:
rules:
- host: cafe.example.com
http:
paths:
- path: /tea
backend:
serviceName: tea-external-svc
servicePort: 81
- path: /coffee
backend:
serviceName: coffee-external-svc
servicePort: 80
#! /bin/bash -ex
kubectl create -f ../hello/cafe.yaml
kubectl create -f ext-svcs.yaml
kubectl create -f cafe-ingress.yaml
apiVersion: v1
kind: Service
metadata:
name: coffee-external-svc
spec:
type: ExternalName
externalName: coffee-svc.default.svc.cluster.local
---
apiVersion: v1
kind: Service
metadata:
name: tea-external-svc
spec:
type: ExternalName
externalName: tea-svc.default.svc.cluster.local
ports:
- port: 81
targetPort: 80
#! /bin/bash -ex
kubectl delete -f cafe-ingress.yaml
kubectl delete -f ext-svcs.yaml
kubectl delete -f ../hello/cafe.yaml
echo "Waiting until varnish-ingress Pods are not ready"
JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'
N=0
until [ $N -ge 120 ]
do
if kubectl get pods -l app=varnish-ingress -o jsonpath="${JSONPATH}" | grep -q '\bReady=True\b'; then
sleep 10
N=$(( N + 10 ))
continue
fi
exit 0
done
echo "Giving up"
exit 1
#! /bin/bash -ex
function killforward {
kill $KUBEPID
}
LOCALPORT=${LOCALPORT:-8888}
kubectl wait --timeout=2m pod -l app=coffee --for=condition=Ready
kubectl wait --timeout=2m pod -l app=tea --for=condition=Ready
kubectl wait --timeout=2m pod -l app=varnish-ingress --for=condition=Ready
kubectl port-forward svc/varnish-ingress ${LOCALPORT}:80 >/dev/null &
KUBEPID=$!
trap killforward EXIT
sleep 1
set +e
N=0
while true; do
sleep 1
cat < /dev/null > /dev/tcp/localhost/${LOCALPORT}
if [ $? -eq 0 ]; then
break
fi
if [ $N -ge 120 ]; then
echo "Giving up"
exit 1
fi
N=$(( N + 1 ))
done
varnishtest ${TESTOPTS} -Dlocalport=${LOCALPORT} ../hello/cafe.vtc
......@@ -202,23 +202,48 @@ func ingMergeError(ings []*extensions.Ingress) error {
return nil
}
func (worker *NamespaceWorker) ingBackend2Addrs(namespace string,
backend extensions.IngressBackend) (addrs []vcl.Address, err error) {
func (worker *NamespaceWorker) ingBackend2Addrs(
namespace string,
backend extensions.IngressBackend,
) (addrs []vcl.Address, extName string, extPort string, err error) {
nsLister := worker.listers.svc.Services(namespace)
svc, err := nsLister.Get(backend.ServiceName)
if err != nil {
return
}
if svc.Spec.Type == api_v1.ServiceTypeExternalName {
if svc.Spec.ExternalName == "" {
return addrs, extName, extPort,
fmt.Errorf("Service %s/%s: ExternalName is "+
"empty for Service Type externalName",
svc.Namespace, svc.Name)
}
extName = svc.Spec.ExternalName
if len(svc.Spec.Ports) > 0 {
idx := 0
for i, port := range svc.Spec.Ports {
if port.Name == "http" {
idx = i
break
}
}
extPort = svc.Spec.Ports[idx].TargetPort.String()
} else {
extPort = backend.ServicePort.String()
}
return
}
endps, err := worker.getServiceEndpoints(svc)
if endps == nil || err != nil {
if endps == nil {
err = fmt.Errorf("could not find endpoints for "+
"service: %s/%s", svc.Namespace, svc.Name)
}
return addrs, fmt.Errorf("Error getting endpoints for service "+
"%v: %v", svc, err)
return addrs, extName, extPort,
fmt.Errorf("Error getting endpoints for service %v: %v",
svc, err)
}
targetPort := int32(0)
......@@ -231,19 +256,22 @@ func (worker *NamespaceWorker) ingBackend2Addrs(namespace string,
targetPort, err = worker.getTargetPort(&port, svc)
if err != nil {
return addrs, fmt.Errorf("Error determining "+
"target port for port %v in Ingress: "+
"%v", ingSvcPort, err)
return addrs, extName, extPort,
fmt.Errorf("Error determining target "+
"port for port %v in Ingress: "+
"%v", ingSvcPort, err)
}
break
}
}
if targetPort == 0 {
return addrs, fmt.Errorf("No port %v in service %s/%s",
ingSvcPort, svc.Namespace, svc.Name)
return addrs, extName, extPort,
fmt.Errorf("No port %v in service %s/%s",
ingSvcPort, svc.Namespace, svc.Name)
}
return endpsTargetPort2Addrs(svc, endps, targetPort)
addrs, err = endpsTargetPort2Addrs(svc, endps, targetPort)
return
}
func getVCLProbe(probe *vcr_v1alpha1.ProbeSpec) *vcl.Probe {
......@@ -274,15 +302,21 @@ func getVCLProbe(probe *vcr_v1alpha1.ProbeSpec) *vcl.Probe {
return vclProbe
}
func (worker *NamespaceWorker) getVCLSvc(svcNamespace string, svcName string,
addrs []vcl.Address) (vcl.Service, *vcr_v1alpha1.BackendConfig, error) {
func (worker *NamespaceWorker) getVCLSvc(
svcNamespace string,
svcName string,
addrs []vcl.Address,
extName string,
extPort string,
) (vcl.Service, *vcr_v1alpha1.BackendConfig, error) {
if svcNamespace == "" {
svcNamespace = "default"
}
vclSvc := vcl.Service{
Name: svcNamespace + "/" + svcName,
Addresses: addrs,
Name: svcNamespace + "/" + svcName,
Addresses: addrs,
ExternalName: extName,
ExternalPort: extPort,
}
nsLister := worker.listers.bcfg.BackendConfigs(svcNamespace)
bcfgs, err := nsLister.List(labels.Everything())
......@@ -337,7 +371,8 @@ func (worker *NamespaceWorker) ings2VCLSpec(
ings []*extensions.Ingress) (vcl.Spec,
map[string]*vcr_v1alpha1.BackendConfig, error) {
vclSpec := vcl.Spec{}
vclSpec.AllServices = make(map[string]vcl.Service)
vclSpec.IntSvcs = make(map[string]vcl.Service)
vclSpec.ExtSvcs = make(map[string]vcl.Service)
bcfgs := make(map[string]*vcr_v1alpha1.BackendConfig)
for _, ing := range ings {
namespace := ing.Namespace
......@@ -353,18 +388,23 @@ func (worker *NamespaceWorker) ings2VCLSpec(
panic("More than one Ingress default backend")
}
backend := ing.Spec.Backend
addrs, err := worker.ingBackend2Addrs(namespace,
*backend)
addrs, extName, extPort, err := worker.ingBackend2Addrs(
namespace, *backend)
if err != nil {
return vclSpec, bcfgs, err
}
vclSvc, bcfg, err := worker.getVCLSvc(namespace,
backend.ServiceName, addrs)
backend.ServiceName, addrs, extName, extPort)
if err != nil {
return vclSpec, bcfgs, err
}
vclSpec.DefaultService = vclSvc
vclSpec.AllServices[namespace+"/"+backend.ServiceName] = vclSvc
key := namespace + "/" + backend.ServiceName
if extName == "" {
vclSpec.IntSvcs[key] = vclSvc
} else {
vclSpec.ExtSvcs[key] = vclSvc
}
if bcfg != nil {
bcfgs[vclSvc.Name] = bcfg
}
......@@ -383,19 +423,26 @@ func (worker *NamespaceWorker) ings2VCLSpec(
continue
}
for _, path := range rule.IngressRuleValue.HTTP.Paths {
addrs, err := worker.ingBackend2Addrs(
namespace, path.Backend)
addrs, extName, extPort, err := worker.
ingBackend2Addrs(namespace,
path.Backend)
if err != nil {
return vclSpec, bcfgs, err
}
vclSvc, bcfg, err := worker.getVCLSvc(namespace,
path.Backend.ServiceName, addrs)
path.Backend.ServiceName, addrs,
extName, extPort)
if err != nil {
return vclSpec, bcfgs, err
}
vclRule.PathMap[path.Path] = vclSvc
vclSpec.AllServices[namespace+"/"+
path.Backend.ServiceName] = vclSvc
key := namespace + "/" +
path.Backend.ServiceName
if extName == "" {
vclSpec.IntSvcs[key] = vclSvc
} else {
vclSpec.ExtSvcs[key] = vclSvc
}
if bcfg != nil {
bcfgs[vclSvc.Name] = bcfg
}
......
......@@ -237,6 +237,10 @@ func (worker *NamespaceWorker) syncSvc(key string) error {
worker.log.Infof("Syncing Varnish Ingress Service %s/%s:",
svc.Namespace, svc.Name)
if svc.Spec.Type == api_v1.ServiceTypeExternalName {
return fmt.Errorf("Viking Service %s/%s: type ExternalName "+
"is illegal", svc.Namespace, svc.Name)
}
// Check if there are Ingresses for which the VCL spec may
// change due to changes in Varnish services.
......
......@@ -598,10 +598,12 @@ func (vc *Controller) updateBeGauges() {
if svc == nil || svc.spec == nil {
continue
}
nBeSvcs += len(svc.spec.spec.AllServices)
for _, beSvc := range svc.spec.spec.AllServices {
nBeSvcs += len(svc.spec.spec.IntSvcs)
for _, beSvc := range svc.spec.spec.IntSvcs {
nBeEndps += len(beSvc.Addresses)
}
nBeSvcs += len(svc.spec.spec.ExtSvcs)
nBeEndps += len(svc.spec.spec.ExtSvcs)
}
beSvcsGauge.Set(float64(nBeSvcs))
beEndpsGauge.Set(float64(nBeEndps))
......
......@@ -109,7 +109,7 @@ var cafeSpec = vcl.Spec{
"/coffee": coffeeSvc,
},
}},
AllServices: map[string]vcl.Service{
IntSvcs: map[string]vcl.Service{
"tea-svc": teaSvc,
"coffee-svc": coffeeSvc,
},
......@@ -156,7 +156,7 @@ var cafeSpecShuf = vcl.Spec{
"/tea": teaSvcShuf,
},
}},
AllServices: map[string]vcl.Service{
IntSvcs: map[string]vcl.Service{
"coffee-svc": coffeeSvcShuf,
"tea-svc": teaSvcShuf,
},
......
/*
* Copyright (c) 2018 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package vcl
import (
"bytes"
"testing"
)
var extWhiskeySvc = Service{
Name: "whiskey-svc",
ExternalName: "whiskey.example.com",
ExternalPort: "81",
}
var extVodkaSvc = Service{
Name: "vodka-svc",
ExternalName: "vodka.example.com",
ExternalPort: "8888",
}
var barSpec = Spec{
DefaultService: Service{},
Rules: []Rule{
{
Host: "cafe.example.com",
PathMap: map[string]Service{
"/tea": teaSvc,
"/coffee": coffeeSvc,
},
},
{
Host: "whiskey.example.com",
PathMap: map[string]Service{
"/": extWhiskeySvc,
},
},
{
Host: "vodka.example.com",
PathMap: map[string]Service{
"/": extVodkaSvc,
},
},
},
IntSvcs: map[string]Service{
"tea-svc": teaSvc,
"coffee-svc": coffeeSvc,
},
ExtSvcs: map[string]Service{
"whiskey-svc": extWhiskeySvc,
"vodka-svc": extVodkaSvc,
},
}
func TestExternalNameSvc(t *testing.T) {
var buf bytes.Buffer
gold := "extname.golden"
if err := ingressTmpl.Execute(&buf, barSpec); err != nil {
t.Fatal("Execute():", err)
}
ok, err := cmpGold(buf.Bytes(), gold)
if err != nil {
t.Fatalf("Reading %s: %v", gold, err)
}
if !ok {
t.Errorf("Generated VCL for IngressSpec does not match gold "+
"file: %s", gold)
if testing.Verbose() {
t.Logf("Generated: %s", buf.String())
}
}
}
......@@ -33,27 +33,45 @@ import (
"testing"
)
var cafeSpec2 = Spec{
var barSpec2 = Spec{
DefaultService: Service{},
Rules: []Rule{{
Host: "cafe.example.com",
PathMap: map[string]Service{
"/tea": teaSvc,
"/coffee": coffeeSvc3,
Rules: []Rule{
{
Host: "cafe.example.com",
PathMap: map[string]Service{
"/tea": teaSvc,
"/coffee": coffeeSvc3,
},
},
{
Host: "whiskey.example.com",
PathMap: map[string]Service{
"/": extWhiskeySvc,
},
},
}},
AllServices: map[string]Service{
{
Host: "vodka.example.com",
PathMap: map[string]Service{
"/": extVodkaSvc,
},
},
},
IntSvcs: map[string]Service{
"tea-svc": teaSvc,
"coffee-svc": coffeeSvc3,
},
ExtSvcs: map[string]Service{
"whiskey-svc": extWhiskeySvc,
"vodka-svc": extVodkaSvc,
},
}
func TestDeepHash(t *testing.T) {
if cafeSpec.DeepHash() == cafeSpec2.DeepHash() {
if barSpec.DeepHash() == barSpec2.DeepHash() {
t.Errorf("DeepHash(): Distinct specs have equal hashes")
if testing.Verbose() {
t.Logf("spec1: %+v", cafeSpec)
t.Logf("spec2: %+v", cafeSpec2)
t.Logf("spec1: %+v", barSpec)
t.Logf("spec2: %+v", barSpec2)
t.Logf("hash: %s", cafeSpec.DeepHash())
}
}
......@@ -91,36 +109,54 @@ var coffeeSvcShuf = Service{
},
}
var cafeSpecShuf = Spec{
var barSpecShuf = Spec{
DefaultService: Service{},
Rules: []Rule{{
Host: "cafe.example.com",
PathMap: map[string]Service{
"/coffee": coffeeSvcShuf,
"/tea": teaSvcShuf,
Rules: []Rule{
{
PathMap: map[string]Service{
"/": extVodkaSvc,
},
Host: "vodka.example.com",
},
{
PathMap: map[string]Service{
"/coffee": coffeeSvcShuf,
"/tea": teaSvcShuf,
},
Host: "cafe.example.com",
},
}},
AllServices: map[string]Service{
{
PathMap: map[string]Service{
"/": extWhiskeySvc,
},
Host: "whiskey.example.com",
},
},
ExtSvcs: map[string]Service{
"vodka-svc": extVodkaSvc,
"whiskey-svc": extWhiskeySvc,
},
IntSvcs: map[string]Service{
"coffee-svc": coffeeSvcShuf,
"tea-svc": teaSvcShuf,
},
}
func TestCanoncial(t *testing.T) {
canonCafe := cafeSpec.Canonical()
canonShuf := cafeSpecShuf.Canonical()
if !reflect.DeepEqual(canonCafe, canonShuf) {
canonBar := barSpec.Canonical()
canonShuf := barSpecShuf.Canonical()
if !reflect.DeepEqual(canonBar, canonShuf) {
t.Error("Canonical(): Equivalent VCL specs not deeply equal")
if testing.Verbose() {
t.Log("Canonical cafe:", canonCafe)
t.Log("Canonical shuffled cafe:", canonShuf)
t.Log("Canonical bar:", canonBar)
t.Log("Canonical shuffled bar:", canonShuf)
}
}
if canonCafe.DeepHash() != canonShuf.DeepHash() {
if canonBar.DeepHash() != canonShuf.DeepHash() {
t.Error("Canonical(): Unequal hashes for equivalent specs")
if testing.Verbose() {
t.Logf("spec1 canonical: %+v", canonCafe)
t.Logf("spec1 hash: %s", canonCafe.DeepHash())
t.Logf("spec1 canonical: %+v", canonBar)
t.Logf("spec1 hash: %s", canonBar.DeepHash())
t.Logf("spec2 canonical: %+v", canonShuf)
t.Logf("spec2 hash: %s", canonShuf.DeepHash())
}
......
......@@ -141,6 +141,8 @@ type Service struct {
Addresses []Address
Probe *Probe
Director *Director
ExternalName string
ExternalPort string
HostHeader string
ConnectTimeout string
FirstByteTimeout string
......@@ -160,6 +162,8 @@ func (svc Service) hash(hash hash.Hash) {
if svc.Director != nil {
svc.Director.hash(hash)
}
hash.Write([]byte(svc.ExternalName))
hash.Write([]byte(svc.ExternalPort))
hash.Write([]byte(svc.HostHeader))
hash.Write([]byte(svc.ConnectTimeout))
hash.Write([]byte(svc.FirstByteTimeout))
......@@ -761,11 +765,14 @@ type Spec struct {
DefaultService Service
// Rules corresponds to the IngressRules in an Ingress.
Rules []Rule
// AllServices is a map of Service names to Service
// configurations for all IngressBackends mentioned in an
// Ingress, including the default Backend, and all Backends to
// which requests are to be routed.
AllServices map[string]Service
// IntSvcs is a map of Service names to Service configurations
// for in-cluster IngressBackends mentioned in an Ingress. May
// include the default Backend.
IntSvcs map[string]Service
// ExtSvcs is a map of Service names to ExternalName Service
// configurations that are IngressBackends mentioned in an
// Ingress.
ExtSvcs map[string]Service
// ShardCluster is derived from the self-sharding
// specification in a VarnishConfig resource.
ShardCluster ShardCluster
......@@ -788,6 +795,20 @@ type Spec struct {
Dispositions []DispositionSpec
}
func hashSvcMap(svcMap map[string]Service, hash hash.Hash) {
svcs := make([]string, len(svcMap))
i := 0
for k := range svcMap {
svcs[i] = k
i++
}
sort.Strings(svcs)
for _, svc := range svcs {
hash.Write([]byte(svc))
svcMap[svc].hash(hash)
}
}
// DeepHash computes a alphanumerically encoded hash value from a Spec
// such that, almost certainly, two Specs are deeply equal iff their
// hash values are equal (unless we've discovered a SHA512 collision).
......@@ -797,17 +818,8 @@ func (spec Spec) DeepHash() string {
for _, rule := range spec.Rules {
rule.hash(hash)
}
svcs := make([]string, len(spec.AllServices))
i := 0
for k := range spec.AllServices {
svcs[i] = k
i++
}
sort.Strings(svcs)
for _, svc := range svcs {
hash.Write([]byte(svc))
spec.AllServices[svc].hash(hash)
}
hashSvcMap(spec.IntSvcs, hash)
hashSvcMap(spec.ExtSvcs, hash)
spec.ShardCluster.hash(hash)
hash.Write([]byte(spec.VCL))
for _, auth := range spec.Auths {
......@@ -835,7 +847,8 @@ func (spec Spec) Canonical() Spec {
canon := Spec{
DefaultService: Service{Name: spec.DefaultService.Name},
Rules: make([]Rule, len(spec.Rules)),
AllServices: make(map[string]Service, len(spec.AllServices)),
IntSvcs: make(map[string]Service, len(spec.IntSvcs)),
ExtSvcs: make(map[string]Service, len(spec.ExtSvcs)),
ShardCluster: spec.ShardCluster,
VCL: spec.VCL,
Auths: make([]Auth, len(spec.Auths)),
......@@ -852,9 +865,13 @@ func (spec Spec) Canonical() Spec {
sort.Stable(byIPPort(svc.Addresses))
}
}
for name, svcs := range spec.AllServices {
canon.AllServices[name] = svcs
sort.Stable(byIPPort(canon.AllServices[name].Addresses))
for name, svcs := range spec.IntSvcs {
canon.IntSvcs[name] = svcs
sort.Stable(byIPPort(canon.IntSvcs[name].Addresses))
}
for name, svcs := range spec.ExtSvcs {
canon.ExtSvcs[name] = svcs
sort.Stable(byIPPort(canon.ExtSvcs[name].Addresses))
}
sort.Stable(byName(canon.ShardCluster.Nodes))
for _, node := range canon.ShardCluster.Nodes {
......
......@@ -3,6 +3,7 @@ vcl 4.1;
import std;
import directors;
import re2;
import dynamic;
backend vk8s_notfound {
# 192.0.2.0/24 reserved for docs & examples (RFC5737).
......
......@@ -3,6 +3,7 @@ vcl 4.1;
import std;
import directors;
import re2;
import dynamic;
backend vk8s_notfound {
# 192.0.2.0/24 reserved for docs & examples (RFC5737).
......
vcl 4.1;
import std;
import directors;
import re2;
import dynamic;
backend vk8s_notfound {
# 192.0.2.0/24 reserved for docs & examples (RFC5737).
.host = "192.0.2.255";
.port = "80";
}
backend vk8s_coffee-svc_192_0_2_4 {
.host = "192.0.2.4";
.port = "80";
}
backend vk8s_coffee-svc_192_0_2_5 {
.host = "192.0.2.5";
.port = "80";
}
backend vk8s_tea-svc_192_0_2_1 {
.host = "192.0.2.1";
.port = "80";
}
backend vk8s_tea-svc_192_0_2_2 {
.host = "192.0.2.2";
.port = "80";
}
backend vk8s_tea-svc_192_0_2_3 {
.host = "192.0.2.3";
.port = "80";
}
sub vcl_init {
new vk8s_hosts = re2.set(anchor=both);
vk8s_hosts.add("\Qcafe.example.com\E(:\d+)?");
vk8s_hosts.add("\Qwhiskey.example.com\E(:\d+)?");
vk8s_hosts.add("\Qvodka.example.com\E(:\d+)?");
vk8s_hosts.compile();
new vk8s_coffee-svc_director = directors.round_robin();
vk8s_coffee-svc_director.add_backend(vk8s_coffee-svc_192_0_2_4
);
vk8s_coffee-svc_director.add_backend(vk8s_coffee-svc_192_0_2_5
);
new vk8s_tea-svc_director = directors.round_robin();
vk8s_tea-svc_director.add_backend(vk8s_tea-svc_192_0_2_1
);
vk8s_tea-svc_director.add_backend(vk8s_tea-svc_192_0_2_2
);
vk8s_tea-svc_director.add_backend(vk8s_tea-svc_192_0_2_3
);
new vk8s_resolver = dynamic.resolver();
vk8s_resolver.set_resolution_type(STUB);
new vk8s_vodka-svc_director = dynamic.director(
ttl_from = dns,
ttl = 30s,
resolver = vk8s_resolver.use()
, port = "8888"
);
new vk8s_whiskey-svc_director = dynamic.director(
ttl_from = dns,
ttl = 30s,
resolver = vk8s_resolver.use()
, port = "81"
);
new vk8s_cafe_example_com_url = re2.set(posix_syntax=true, anchor=start);
vk8s_cafe_example_com_url.add("/coffee",
backend=vk8s_coffee-svc_director.backend());
vk8s_cafe_example_com_url.add("/tea",
backend=vk8s_tea-svc_director.backend());
vk8s_cafe_example_com_url.compile();
new vk8s_whiskey_example_com_url = re2.set(posix_syntax=true, anchor=start);
vk8s_whiskey_example_com_url.add("/",
backend=vk8s_whiskey-svc_director.backend("whiskey.example.com"));
vk8s_whiskey_example_com_url.compile();
new vk8s_vodka_example_com_url = re2.set(posix_syntax=true, anchor=start);
vk8s_vodka_example_com_url.add("/",
backend=vk8s_vodka-svc_director.backend("vodka.example.com"));
vk8s_vodka_example_com_url.compile();
}
sub vk8s_set_backend {
set req.backend_hint = vk8s_notfound;
if (vk8s_hosts.match(req.http.Host)) {
if (vk8s_hosts.nmatches() != 1) {
# Fail fast when the match was not unique.
return (fail);
}
if (0 != 0) {
#
}
elsif (vk8s_hosts.which() == 1) {
if (vk8s_cafe_example_com_url.match(req.url)) {
set req.backend_hint = vk8s_cafe_example_com_url.backend(select=FIRST);
}
}
elsif (vk8s_hosts.which() == 2) {
if (vk8s_whiskey_example_com_url.match(req.url)) {
set req.backend_hint = vk8s_whiskey_example_com_url.backend(select=FIRST);
}
}
elsif (vk8s_hosts.which() == 3) {
if (vk8s_vodka_example_com_url.match(req.url)) {
set req.backend_hint = vk8s_vodka_example_com_url.backend(select=FIRST);
}
}
}
if (req.backend_hint == vk8s_notfound) {
return (synth(404));
}
}
sub vcl_miss {
call vk8s_set_backend;
}
sub vcl_pass {
call vk8s_set_backend;
}
sub vcl_pipe {
call vk8s_set_backend;
}
sub vcl_hit {
if (obj.ttl < 0s) {
# Set a backend for a background fetch.
call vk8s_set_backend;
}
}
......@@ -3,6 +3,7 @@ vcl 4.1;
import std;
import directors;
import re2;
import dynamic;
backend vk8s_notfound {
# 192.0.2.0/24 reserved for docs & examples (RFC5737).
......
......@@ -3,6 +3,7 @@ vcl 4.1;
import std;
import directors;
import re2;
import dynamic;
backend vk8s_notfound {
# 192.0.2.0/24 reserved for docs & examples (RFC5737).
......
......@@ -3,6 +3,7 @@ vcl 4.1;
import std;
import directors;
import re2;
import dynamic;
backend vk8s_notfound {
# 192.0.2.0/24 reserved for docs & examples (RFC5737).
......
......@@ -3,6 +3,7 @@ vcl 4.1;
import std;
import directors;
import re2;
import dynamic;
backend vk8s_notfound {
# 192.0.2.0/24 reserved for docs & examples (RFC5737).
......@@ -10,7 +11,7 @@ backend vk8s_notfound {
.port = "80";
}
{{- range $name, $svc := .AllServices}}
{{- range $name, $svc := .IntSvcs}}
{{- if $svc.Probe}}
{{with $svc.Probe}}
probe {{probeName $name}} {
......@@ -46,7 +47,7 @@ probe {{probeName $name}} {
{{- end}}
{{- end}}
{{range $name, $svc := .AllServices -}}
{{range $name, $svc := .IntSvcs -}}
{{range $addr := $svc.Addresses -}}
backend {{backendName $svc $addr.IP}} {
.host = "{{$addr.IP}}";
......@@ -87,7 +88,7 @@ sub vcl_init {
vk8s_hosts.compile();
{{end}}
{{- range $name, $svc := .AllServices}}
{{- range $name, $svc := .IntSvcs}}
{{- $dirType := dirType $svc}}
new {{dirName $svc}} = directors.{{$dirType}}();
{{- range $addr := $svc.Addresses}}
......@@ -107,11 +108,52 @@ sub vcl_init {
{{dirName $svc}}.reconfigure();
{{- end}}
{{end}}
{{- if gt (len .ExtSvcs) 0 }}
new vk8s_resolver = dynamic.resolver();
vk8s_resolver.set_resolution_type(STUB);
{{- end}}
{{- range $name, $svc := .ExtSvcs}}
new {{dirName $svc}} = dynamic.director(
ttl_from = dns,
ttl = 30s,
resolver = vk8s_resolver.use()
{{- with $svc}}
{{- if .ExternalPort}}
, port = "{{.ExternalPort}}"
{{- end}}
{{- if .HostHeader}}
, host_header = "{{.HostHeader}}"
{{- end}}
{{- if .ConnectTimeout}}
, connect_timeout = {{.ConnectTimeout}}
{{- end}}
{{- if .FirstByteTimeout}}
, first_byte_timeout = {{.FirstByteTimeout}}
{{- end}}
{{- if .BetweenBytesTimeout}}
, between_bytes_timeout = {{.BetweenBytesTimeout}}
{{- end}}
{{- if .ProxyHeader}}
, proxy_header = {{.ProxyHeader}}
{{- end}}
{{- if .MaxConnections}}
, max_connections = {{.MaxConnections}}
{{- end}}
{{- if .Probe}}
, probe = {{probeName $name}}
{{- end}}
{{- end}}
);
{{end}}
{{- range $rule := .Rules}}
new {{urlMatcher $rule}} = re2.set(posix_syntax=true, anchor=start);
{{- range $path, $svc := $rule.PathMap}}
{{urlMatcher $rule}}.add("{{$path}}",
{{- if $svc.ExternalName}}
backend={{dirName $svc}}.backend("{{$svc.ExternalName}}"));
{{- else}}
backend={{dirName $svc}}.backend());
{{- end}}
{{- end}}
{{urlMatcher $rule}}.compile();
{{end -}}
......
......@@ -102,7 +102,7 @@ var cafeSpec = Spec{
"/coffee": coffeeSvc,
},
}},
AllServices: map[string]Service{
IntSvcs: map[string]Service{
"tea-svc": teaSvc,
"coffee-svc": coffeeSvc,
},
......@@ -597,7 +597,7 @@ var customVCLSpec = Spec{
"/coffee": coffeeSvc,
},
}},
AllServices: map[string]Service{
IntSvcs: map[string]Service{
"tea-svc": teaSvc,
"coffee-svc": coffeeSvc,
},
......@@ -734,7 +734,7 @@ var cafeProbeDir = Spec{
"/milk": milkSvcProbeDir,
},
}},
AllServices: map[string]Service{
IntSvcs: map[string]Service{
"tea-svc": teaSvcProbeDir,
"coffee-svc": coffeeSvcProbeDir,
"milk-svc": milkSvcProbeDir,
......
......@@ -163,4 +163,30 @@ cd ${MYPATH}/e2e/deleteTLSsecret
./verify.sh
./undeploy.sh
echo Example of an ExternalName Service as an Ingress backend
cd ${MYPATH}/../examples/externalname/
./deploy.sh
./verify.sh
./undeploy.sh
# Now re-deploy and verify again -- the Service is assigned a new IP
# address, verify that the new address is resolved.
# Since the Service does not exist for a brief time, DNS lookups get
# negative results during this time. We allow verification failures
# for up to 2 minutes.
./deploy.sh
N=0
while true; do
if ! ./verify.sh; then
if [ $N -gt 120 ]; then
exit 1
fi
N=$(( N + 10 ))
continue
else
break
fi
done
./undeploy.sh
exit 0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment