Skip to content

Commit

Permalink
Implement Kubernetes service deployer (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
mtojek authored Feb 16, 2021
1 parent e481ad2 commit 98adbcd
Show file tree
Hide file tree
Showing 31 changed files with 640 additions and 18,500 deletions.
28 changes: 25 additions & 3 deletions .ci/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pipeline {
JOB_GIT_CREDENTIALS = "f6c7695a-671e-4f4f-a331-acdce44ff9ba"
PIPELINE_LOG_LEVEL='INFO'
AWS_ACCOUNT_SECRET = 'secret/observability-team/ci/elastic-observability-aws-account-auth'
HOME = "${env.WORKSPACE}"
KIND_VERSION = 'v0.10.0'
K8S_VERSION = 'v1.20.2'
}
options {
timeout(time: 1, unit: 'HOURS')
Expand Down Expand Up @@ -41,9 +44,11 @@ pipeline {
steps {
cleanup()
withMageEnv(){
withCloudTestEnv() {
dir("${BASE_DIR}"){
sh(label: 'Check',script: 'make check')
withKubernetes() {
withCloudTestEnv() {
dir("${BASE_DIR}"){
sh(label: 'Check',script: 'make check')
}
}
}
}
Expand All @@ -52,6 +57,7 @@ pipeline {
always {
dir("${BASE_DIR}") {
archiveArtifacts(allowEmptyArchive: true, artifacts: 'build/test-results/*.xml')
archiveArtifacts(allowEmptyArchive: true, artifacts: 'build/kubectl-dump.txt')
archiveArtifacts(allowEmptyArchive: true, artifacts: 'build/elastic-stack-dump/stack/logs/*.log')
archiveArtifacts(allowEmptyArchive: true, artifacts: 'build/elastic-stack-dump/check/logs/*.log')
junit(allowEmptyResults: false,
Expand All @@ -76,6 +82,22 @@ def cleanup(){
unstash 'source'
}

def withKubernetes(Closure body) {
retryWithSleep(retries: 2, seconds: 5, backoff: true) { sh(label: "Install kind", script: '''
mkdir -p ${HOME}/bin
curl -sSLo ${HOME}/bin/kind "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
chmod +x ${HOME}/bin/kind
kind version
''') }
retryWithSleep(retries: 2, seconds: 5, backoff: true) { sh(label: "Install kubectl", script: '''
mkdir -p ${HOME}/bin
curl -sSLo ${HOME}/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl"
chmod +x ${HOME}/bin/kubectl
kubectl version --client
''') }
body()
}

def withCloudTestEnv(Closure body) {
def maskedVars = []
// AWS
Expand Down
6 changes: 4 additions & 2 deletions cmd/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"strings"

"github.com/elastic/elastic-package/internal/install"

"github.com/pkg/errors"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -86,7 +88,7 @@ func setupStackCommand() *cobra.Command {
upCommand.Flags().BoolP(cobraext.DaemonModeFlagName, "d", false, cobraext.DaemonModeFlagDescription)
upCommand.Flags().StringSliceP(cobraext.StackServicesFlagName, "s", nil,
fmt.Sprintf(cobraext.StackServicesFlagDescription, strings.Join(availableServicesAsList(), ",")))
upCommand.Flags().StringP(cobraext.StackVersionFlagName, "", stack.DefaultVersion, cobraext.StackVersionFlagDescription)
upCommand.Flags().StringP(cobraext.StackVersionFlagName, "", install.DefaultStackVersion, cobraext.StackVersionFlagDescription)

downCommand := &cobra.Command{
Use: "down",
Expand Down Expand Up @@ -126,7 +128,7 @@ func setupStackCommand() *cobra.Command {
return nil
},
}
updateCommand.Flags().StringP(cobraext.StackVersionFlagName, "", stack.DefaultVersion, cobraext.StackVersionFlagDescription)
updateCommand.Flags().StringP(cobraext.StackVersionFlagName, "", install.DefaultStackVersion, cobraext.StackVersionFlagDescription)

shellInitCommand := &cobra.Command{
Use: "shellinit",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/elastic/go-elasticsearch/v7 v7.9.0
github.com/elastic/go-licenser v0.3.1
github.com/elastic/go-ucfg v0.8.3
github.com/elastic/package-spec/code/go v0.0.0-20210204181807-877461fbcf74
github.com/elastic/package-spec/code/go v0.0.0-20210210092720-cd2c4d743438
github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.1.0
github.com/go-openapi/strfmt v0.19.6 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ github.com/elastic/go-licenser v0.3.1 h1:RmRukU/JUmts+rpexAw0Fvt2ly7VVu6mw8z4HrE
github.com/elastic/go-licenser v0.3.1/go.mod h1:D8eNQk70FOCVBl3smCGQt/lv7meBeQno2eI1S5apiHQ=
github.com/elastic/go-ucfg v0.8.3 h1:leywnFjzr2QneZZWhE6uWd+QN/UpP0sdJRHYyuFvkeo=
github.com/elastic/go-ucfg v0.8.3/go.mod h1:iaiY0NBIYeasNgycLyTvhJftQlQEUO2hpF+FX0JKxzo=
github.com/elastic/package-spec/code/go v0.0.0-20210204181807-877461fbcf74 h1:bEccXQs1baxo7DEf3eWQZ0UT47ZYWlTqijaY5aGpWyM=
github.com/elastic/package-spec/code/go v0.0.0-20210204181807-877461fbcf74/go.mod h1:dog1l3e8NoRYxuB8yIbbOWglE6GSQuU6ZL75wT9pKL8=
github.com/elastic/package-spec/code/go v0.0.0-20210210092720-cd2c4d743438 h1:Vsz250gTJcYn3zBL6J7u1ERdjjnQlcG177ksaEroNWs=
github.com/elastic/package-spec/code/go v0.0.0-20210210092720-cd2c4d743438/go.mod h1:dog1l3e8NoRYxuB8yIbbOWglE6GSQuU6ZL75wT9pKL8=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
Expand Down
61 changes: 61 additions & 0 deletions internal/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
package docker

import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"

Expand All @@ -13,6 +16,13 @@ import (
"github.com/elastic/elastic-package/internal/logger"
)

// NetworkDescription describes the Docker network and connected Docker containers.
type NetworkDescription struct {
Containers map[string]struct {
Name string
}
}

// Pull downloads the latest available revision of the image.
func Pull(image string) error {
cmd := exec.Command("docker", "pull", image)
Expand All @@ -29,3 +39,54 @@ func Pull(image string) error {
}
return nil
}

// ContainerID function returns the container ID for a given container name.
func ContainerID(containerName string) (string, error) {
cmd := exec.Command("docker", "ps", "--filter", "name="+containerName, "--format", "{{.ID}}")
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput

logger.Debugf("output command: %s", cmd)
output, err := cmd.Output()
if err != nil {
return "", errors.Wrapf(err, "could not find \"%s\" container (stderr=%q)", containerName, errOutput.String())
}
containerIDs := bytes.Split(bytes.TrimSpace(output), []byte{'\n'})
if len(containerIDs) != 1 {
return "", fmt.Errorf("expected single %s container", containerName)
}
return string(containerIDs[0]), nil
}

// InspectNetwork function returns the network description for the selected network.
func InspectNetwork(network string) ([]NetworkDescription, error) {
cmd := exec.Command("docker", "network", "inspect", network)
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput

logger.Debugf("output command: %s", cmd)
output, err := cmd.Output()
if err != nil {
return nil, errors.Wrapf(err, "could not inspect the network (stderr=%q)", errOutput.String())
}

var networkDescriptions []NetworkDescription
err = json.Unmarshal(output, &networkDescriptions)
if err != nil {
return nil, errors.Wrapf(err, "can't unmarshal network inspect for %s (stderr=%q)", network, errOutput.String())
}
return networkDescriptions, nil
}

// ConnectToNetwork function connects the container to the selected Docker network.
func ConnectToNetwork(containerID, network string) error {
cmd := exec.Command("docker", "network", "connect", network, containerID)
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput

logger.Debugf("output command: %s", cmd)
if err := cmd.Run(); err != nil {
return errors.Wrapf(err, "could not attach container to the stack network (stderr=%q)", errOutput.String())
}
return nil
}
38 changes: 35 additions & 3 deletions internal/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/pkg/errors"
)
Expand All @@ -20,12 +21,14 @@ const (
temporaryDir = "tmp"
deployerDir = "deployer"

terraformDeployerYmlFile = "terraform-deployer.yml"
kubernetesDeployerElasticAgentYmlFile = "elastic-agent.yml"
terraformDeployerYmlFile = "terraform-deployer.yml"
)

var (
serviceLogsDir = filepath.Join(temporaryDir, "service_logs")
terraformDeployerDir = filepath.Join(deployerDir, "terraform")
serviceLogsDir = filepath.Join(temporaryDir, "service_logs")
kubernetesDeployerDir = filepath.Join(deployerDir, "kubernetes")
terraformDeployerDir = filepath.Join(deployerDir, "terraform")
)

const versionFilename = "version"
Expand Down Expand Up @@ -57,6 +60,11 @@ func EnsureInstalled() error {
return errors.Wrap(err, "writing stack resources failed")
}

err = writeKubernetesDeployerResources(elasticPackagePath)
if err != nil {
return errors.Wrap(err, "writing Kubernetes deployer resources failed")
}

err = writeTerraformDeployerResources(elasticPackagePath)
if err != nil {
return errors.Wrap(err, "writing Terraform deployer resources failed")
Expand Down Expand Up @@ -107,6 +115,15 @@ func TerraformDeployerComposeFile() (string, error) {
return filepath.Join(configurationDir, terraformDeployerDir, terraformDeployerYmlFile), nil
}

// KubernetesDeployerElasticAgentFile function returns the path to the Elastic Agent YAML definition for the Kubernetes cluster.
func KubernetesDeployerElasticAgentFile() (string, error) {
configurationDir, err := configurationDir()
if err != nil {
return "", errors.Wrap(err, "locating configuration directory failed")
}
return filepath.Join(configurationDir, kubernetesDeployerDir, kubernetesDeployerElasticAgentYmlFile), nil
}

func configurationDir() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
Expand Down Expand Up @@ -157,6 +174,21 @@ func writeStackResources(elasticPackagePath string) error {
return nil
}

func writeKubernetesDeployerResources(elasticPackagePath string) error {
kubernetesDeployer := filepath.Join(elasticPackagePath, kubernetesDeployerDir)
err := os.MkdirAll(kubernetesDeployer, 0755)
if err != nil {
return errors.Wrapf(err, "creating directory failed (path: %s)", kubernetesDeployer)
}

err = writeStaticResource(err, filepath.Join(kubernetesDeployer, kubernetesDeployerElasticAgentYmlFile),
strings.ReplaceAll(kubernetesDeployerElasticAgentYml, "{{ STACK_VERSION }}", DefaultStackVersion))
if err != nil {
return errors.Wrap(err, "writing static resource failed")
}
return nil
}

func writeTerraformDeployerResources(elasticPackagePath string) error {
terraformDeployer := filepath.Join(elasticPackagePath, terraformDeployerDir)
err := os.MkdirAll(terraformDeployer, 0755)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package stack
package install

const (
// DefaultVersion is the default version of the stack
DefaultVersion = "7.12.0-SNAPSHOT"
// DefaultStackVersion is the default version of the stack
DefaultStackVersion = "7.12.0-SNAPSHOT"
)
125 changes: 125 additions & 0 deletions internal/install/static_kubernetes_elastic_agent_yml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package install

const kubernetesDeployerElasticAgentYml = `---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kind-fleet-agent-clusterscope
namespace: kube-system
labels:
app: kind-fleet-agent-clusterscope
group: fleet
spec:
selector:
matchLabels:
app: kind-fleet-agent-clusterscope
template:
metadata:
labels:
app: kind-fleet-agent-clusterscope
group: fleet
spec:
serviceAccountName: kind-fleet-agent
containers:
- name: kind-fleet-agent-clusterscope
image: docker.elastic.co/beats/elastic-agent:{{ STACK_VERSION }}
env:
- name: FLEET_ENROLL
value: "1"
- name: FLEET_ENROLL_INSECURE
value: "1"
- name: KIBANA_HOST
value: "http://kibana:5601"
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
securityContext:
runAsUser: 0
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: kind-fleet-agent-clusterscope
namespace: kube-system
labels:
group: fleet
data:
elastic-agent.yml: |-
management:
mode: "fleet"
grpc:
# listen address for the GRPC server that spawned processes connect back to.
address: localhost
# port for the GRPC server that spawned processes connect back to.
port: 6789
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kind-fleet-agent
subjects:
- kind: ServiceAccount
name: kind-fleet-agent
namespace: kube-system
roleRef:
kind: ClusterRole
name: kind-fleet-agent
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kind-fleet-agent
labels:
k8s-app: kind-fleet-agent
rules:
- apiGroups: [""]
resources:
- nodes
- namespaces
- events
- pods
- secrets
verbs: ["get", "list", "watch"]
- apiGroups: ["extensions"]
resources:
- replicasets
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources:
- statefulsets
- deployments
- replicasets
verbs: ["get", "list", "watch"]
- apiGroups:
- ""
resources:
- nodes/stats
verbs:
- get
# required for apiserver
- nonResourceURLs:
- "/metrics"
verbs:
- get
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kind-fleet-agent
namespace: kube-system
labels:
k8s-app: kind-fleet-agent
---
`
Loading

0 comments on commit 98adbcd

Please sign in to comment.