Skip to content

Added Database external access service feature #126

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

Merged
merged 5 commits into from
Apr 20, 2018
Merged
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
26 changes: 26 additions & 0 deletions docs/Manual/Deployment/Kubernetes/DeploymentResource.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,32 @@ The encryption key cannot be changed after the cluster has been created.
The secret specified by this setting, must have a data field named 'key' containing
an encryption key that is exactly 32 bytes long.

### `spec.externalAccess.type: string`

This setting specifies the type of `Service` that will be created to provide
access to the ArangoDB deployment from outside the Kubernetes cluster.
Possible values are:

- `None` To limit access to application running inside the Kubernetes cluster.
- `LoadBalancer` To create a `Service` of type `LoadBalancer` for the ArangoDB deployment.
- `NodePort` To create a `Service` of type `NodePort` for the ArangoDB deployment.
- `Auto` (default) To create a `Service` of type `LoadBalancer` and fallback to a `Service` or type `NodePort` when the
`LoadBalancer` is not assigned an IP address.

### `spec.externalAccess.loadBalancerIP: string`

This setting specifies the IP used to for the LoadBalancer to expose the ArangoDB deployment on.
This setting is used when `spec.externalAccess.type` is set to `LoadBalancer` or `Auto`.

If you do not specify this setting, an IP will be chosen automatically by the load-balancer provisioner.

### `spec.externalAccess.nodePort: int`

This setting specifies the port used to expose the ArangoDB deployment on.
This setting is used when `spec.externalAccess.type` is set to `NodePort` or `Auto`.

If you do not specify this setting, a random port will be chosen automatically.

### `spec.auth.jwtSecretName: string`

This setting specifies the name of a kubernetes `Secret` that contains
Expand Down
28 changes: 28 additions & 0 deletions docs/Manual/Deployment/Kubernetes/ServicesAndLoadBalancer.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,39 @@
The ArangoDB Kubernetes Operator will create services that can be used to
reach the ArangoDB servers from inside the Kubernetes cluster.

By default, the ArangoDB Kubernetes Operator will also create an additional
service to reach the ArangoDB deployment from outside the Kubernetes cluster.

For exposing the ArangoDB deployment to the outside, there are 2 options:

- Using a `NodePort` service. This will expose the deployment on a specific port (above 30.000)
on all nodes of the Kubernetes cluster.
- Using a `LoadBalancer` service. This will expose the deployment on a load-balancer
that is provisioned by the Kubernetes cluster.

The `LoadBalancer` option is the most convenient, but not all Kubernetes clusters
are able to provision a load-balancer. Therefore we offer a third (and default) option: `Auto`.
In this option, the ArangoDB Kubernetes Operator tries to create a `LoadBalancer`
service. It then waits for up to a minute for the Kubernetes cluster to provision
a load-balancer for it. If that has not happened after a minute, the service
is replaced by a service of type `NodePort`.

To inspect the created service, run:

```bash
kubectl get services <deployment-name>-ea
```

To use the ArangoDB servers from outside the Kubernetes cluster
you have to add another service as explained below.

## Services

If you do not want the ArangoDB Kubernetes Operator to create an external-access
service for you, set `spec.externalAccess.Type` to `None`.

If you want to create external access services manually, follow the instructions below.

### Single server

For a single server deployment, the operator creates a single
Expand Down
74 changes: 14 additions & 60 deletions docs/Manual/Tutorials/Kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,44 +107,26 @@ your database s available.
## Connecting to your database

The single server database you deployed in the previous chapter is now
available, but only from within the Kubernetes cluster.
available from within the Kubernetes cluster as well as outside it.

To make the database available outside your Kubernetes cluster (e.g. for browser access)
you must deploy an additional `Service`.
Access to the database from outside the Kubernetes cluster is provided
using an external-access service.
By default this service is of type `LoadBalancer`. If this type of service
is not supported by your Kubernetes cluster, it will be replaced by
a service of type `NodePort` after a minute.

There are several possible types of `Service` to choose from.
We are going to use the `NodePort` type to expose the database on port 30529 of
every node of your Kubernetes cluster.

Create a file called `single-server-service.yaml` with the following content.

```yaml
kind: Service
apiVersion: v1
metadata:
name: single-server-service
spec:
selector:
app: arangodb
arango_deployment: single-server
role: single
type: NodePort
ports:
- protocol: TCP
port: 8529
targetPort: 8529
nodePort: 30529
```

Deploy the `Service` into your Kubernetes cluster using:
To see the type of service that has been created, run:

```bash
kubectl apply -f single-server-service.yaml
kubectl get service single-server-ea
```

Now you can connect your browser to `https://<node name>:30529/`,
where `<node name>` is the name or IP address of any of the nodes
of your Kubernetes cluster.
When the service is of the `LoadBalancer` type, use the IP address
listed in the `EXTERNAL-IP` column with port 8529.
When the service is of the `NodePort` type, use the IP address
of any of the nodes of the cluster, combine with the high (>30000) port listed in the `PORT(S)` column.

Now you can connect your browser to `https://<ip>:<port>/`.

Your browser will show a warning about an unknown certificate.
Accept the certificate for now.
Expand Down Expand Up @@ -183,34 +165,6 @@ kubectl apply -f cluster.yaml
The same commands used in the single server deployment can be used
to inspect your cluster. Just use the correct deployment name (`cluster` instead of `single-server`).

Connecting to your cluster requires a different `Service` since the
selector now has to select your `cluster` deployment and instead
of selecting all `Pods` with role `single` it will have to select
all coordinator pods.

The service looks like this:

```yaml
kind: Service
apiVersion: v1
metadata:
name: cluster-service
spec:
selector:
app: arangodb
arango_deployment: cluster
role: coordinator
type: NodePort
ports:
- protocol: TCP
port: 8529
targetPort: 8529
nodePort: 31529
```

Note that we have chosen a different node port (31529) for this `Service`
to avoid conflicts with the port used in `single-server-service`.

## Where to go from here

- [ArangoDB Kubernetes Operator](../../Deployment/Kubernetes/README.md)
9 changes: 9 additions & 0 deletions pkg/apis/deployment/v1alpha/deployment_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type DeploymentSpec struct {
Image *string `json:"image,omitempty"`
ImagePullPolicy *v1.PullPolicy `json:"imagePullPolicy,omitempty"`

ExternalAccess ExternalAccessSpec `json:"externalAccess"`
RocksDB RocksDBSpec `json:"rocksdb"`
Authentication AuthenticationSpec `json:"auth"`
TLS TLSSpec `json:"tls"`
Expand Down Expand Up @@ -139,6 +140,7 @@ func (s *DeploymentSpec) SetDefaults(deploymentName string) {
if s.GetImagePullPolicy() == "" {
s.ImagePullPolicy = util.NewPullPolicy(v1.PullIfNotPresent)
}
s.ExternalAccess.SetDefaults()
s.RocksDB.SetDefaults()
s.Authentication.SetDefaults(deploymentName + "-jwt")
s.TLS.SetDefaults(deploymentName + "-ca")
Expand Down Expand Up @@ -169,6 +171,7 @@ func (s *DeploymentSpec) SetDefaultsFrom(source DeploymentSpec) {
if s.ImagePullPolicy == nil {
s.ImagePullPolicy = util.NewPullPolicyOrNil(source.ImagePullPolicy)
}
s.ExternalAccess.SetDefaultsFrom(source.ExternalAccess)
s.RocksDB.SetDefaultsFrom(source.RocksDB)
s.Authentication.SetDefaultsFrom(source.Authentication)
s.TLS.SetDefaultsFrom(source.TLS)
Expand Down Expand Up @@ -200,6 +203,9 @@ func (s *DeploymentSpec) Validate() error {
if s.GetImage() == "" {
return maskAny(errors.Wrapf(ValidationError, "spec.image must be set"))
}
if err := s.ExternalAccess.Validate(); err != nil {
return maskAny(errors.Wrap(err, "spec.externalAccess"))
}
if err := s.RocksDB.Validate(); err != nil {
return maskAny(errors.Wrap(err, "spec.rocksdb"))
}
Expand Down Expand Up @@ -254,6 +260,9 @@ func (s DeploymentSpec) ResetImmutableFields(target *DeploymentSpec) []string {
target.StorageEngine = NewStorageEngineOrNil(s.StorageEngine)
resetFields = append(resetFields, "storageEngine")
}
if l := s.ExternalAccess.ResetImmutableFields("externalAccess", &target.ExternalAccess); l != nil {
resetFields = append(resetFields, l...)
}
if l := s.RocksDB.ResetImmutableFields("rocksdb", &target.RocksDB); l != nil {
resetFields = append(resetFields, l...)
}
Expand Down
84 changes: 84 additions & 0 deletions pkg/apis/deployment/v1alpha/external_access_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//

package v1alpha

import (
"github.com/arangodb/kube-arangodb/pkg/util"
)

// ExternalAccessSpec holds configuration for the external access provided for the deployment.
type ExternalAccessSpec struct {
// Type of external access
Type *ExternalAccessType `json:"type,omitempty"`
// Optional port used in case of Auto or NodePort type.
NodePort *int `json:"nodePort,omitempty"`
// Optional IP used to configure a load-balancer on, in case of Auto or LoadBalancer type.
LoadBalancerIP *string `json:"loadBalancerIP,omitempty"`
}

// GetType returns the value of type.
func (s ExternalAccessSpec) GetType() ExternalAccessType {
return ExternalAccessTypeOrDefault(s.Type, ExternalAccessTypeAuto)
}

// GetNodePort returns the value of nodePort.
func (s ExternalAccessSpec) GetNodePort() int {
return util.IntOrDefault(s.NodePort)
}

// GetLoadBalancerIP returns the value of loadBalancerIP.
func (s ExternalAccessSpec) GetLoadBalancerIP() string {
return util.StringOrDefault(s.LoadBalancerIP)
}

// Validate the given spec
func (s ExternalAccessSpec) Validate() error {
if err := s.GetType().Validate(); err != nil {
return maskAny(err)
}
return nil
}

// SetDefaults fills in missing defaults
func (s *ExternalAccessSpec) SetDefaults() {
}

// SetDefaultsFrom fills unspecified fields with a value from given source spec.
func (s *ExternalAccessSpec) SetDefaultsFrom(source ExternalAccessSpec) {
if s.Type == nil {
s.Type = NewExternalAccessTypeOrNil(source.Type)
}
if s.NodePort == nil {
s.NodePort = util.NewIntOrNil(source.NodePort)
}
if s.LoadBalancerIP == nil {
s.LoadBalancerIP = util.NewStringOrNil(source.LoadBalancerIP)
}
}

// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
// It returns a list of fields that have been reset.
// Field names are relative to given field prefix.
func (s ExternalAccessSpec) ResetImmutableFields(fieldPrefix string, target *ExternalAccessSpec) []string {
return nil
}
95 changes: 95 additions & 0 deletions pkg/apis/deployment/v1alpha/external_access_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//

package v1alpha

import (
"github.com/pkg/errors"
"k8s.io/api/core/v1"
)

// ExternalAccessType specifies the type of external access provides for the deployment
type ExternalAccessType string

const (
// ExternalAccessTypeNone yields a cluster with no external access
ExternalAccessTypeNone ExternalAccessType = "None"
// ExternalAccessTypeAuto yields a cluster with an automatic selection for external access
ExternalAccessTypeAuto ExternalAccessType = "Auto"
// ExternalAccessTypeLoadBalancer yields a cluster with a service of type `LoadBalancer` to provide external access
ExternalAccessTypeLoadBalancer ExternalAccessType = "LoadBalancer"
// ExternalAccessTypeNodePort yields a cluster with a service of type `NodePort` to provide external access
ExternalAccessTypeNodePort ExternalAccessType = "NodePort"
)

func (t ExternalAccessType) IsNone() bool { return t == ExternalAccessTypeNone }
func (t ExternalAccessType) IsAuto() bool { return t == ExternalAccessTypeAuto }
func (t ExternalAccessType) IsLoadBalancer() bool { return t == ExternalAccessTypeLoadBalancer }
func (t ExternalAccessType) IsNodePort() bool { return t == ExternalAccessTypeNodePort }

// AsServiceType returns the k8s ServiceType for this ExternalAccessType.
// If type is "Auto", ServiceTypeLoadBalancer is returned.
func (t ExternalAccessType) AsServiceType() v1.ServiceType {
switch t {
case ExternalAccessTypeLoadBalancer, ExternalAccessTypeAuto:
return v1.ServiceTypeLoadBalancer
case ExternalAccessTypeNodePort:
return v1.ServiceTypeNodePort
default:
return ""
}
}

// Validate the type.
// Return errors when validation fails, nil on success.
func (t ExternalAccessType) Validate() error {
switch t {
case ExternalAccessTypeNone, ExternalAccessTypeAuto, ExternalAccessTypeLoadBalancer, ExternalAccessTypeNodePort:
return nil
default:
return maskAny(errors.Wrapf(ValidationError, "Unknown external access type: '%s'", string(t)))
}
}

// NewExternalAccessType returns a reference to a string with given value.
func NewExternalAccessType(input ExternalAccessType) *ExternalAccessType {
return &input
}

// NewExternalAccessTypeOrNil returns nil if input is nil, otherwise returns a clone of the given value.
func NewExternalAccessTypeOrNil(input *ExternalAccessType) *ExternalAccessType {
if input == nil {
return nil
}
return NewExternalAccessType(*input)
}

// ExternalAccessTypeOrDefault returns the default value (or empty string) if input is nil, otherwise returns the referenced value.
func ExternalAccessTypeOrDefault(input *ExternalAccessType, defaultValue ...ExternalAccessType) ExternalAccessType {
if input == nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return ""
}
return *input
}
Loading