linkerd2/BUILD.md

13 KiB

Conduit Development Guide

🎈 Welcome to the Conduit development guide! 👋

This document will help you build, run, and test Conduit from source.

Table of contents

Repo layout

Conduit is primarily written in Rust, Go, and React. At its core is a high-performance data plane written in Rust. The control plane components are written in Go. The dashboard UI is a React application.

Control Plane (Go/React)

  • cli: Command-line conduit utility, view and drive the control plane.
  • controller
    • destination: Serves service discovery information to the proxy.
    • proxy-api: Accepts requests from proxy instances and forwards those requests to the appropriate controller service.
    • public-api: Accepts requests from API clients such as cli and web, provides access to and control of the conduit service mesh.
    • tap: Provides a live pipeline of requests.
    • telemetry: Collects and aggregates metrics from proxy componenets.
  • proxy-init: Adds a Kubernetes pod to join the Conduit Service Mesh.
  • web: Provides a UI dashboard to view and drive the control plane. This component is written in Go and React.

Data Plane (Rust)

  • proxy: High-performance data plane, injected as a sidecar with every service.
  • tower-grpc: A client and server gRPC implementation based on Tower.
  • tower-grpc-examples: Demonstrates how to use Tower gRPC clients and servers with code generation.
  • tower-h2: Tower Service abstractions for HTTP/2 and Rust.
  • tower-router: A Tower middleware that routes requests to one of a set of inner services using a request predicate.

Components

Conduit Components

conduit_components digraph G { rankdir=LR;
node [style=filled, shape=rect];

"cli" [color=lightblue];
"destination" [color=lightblue];
"proxy-api" [color=lightblue];
"public-api" [color=lightblue];
"tap" [color=lightblue];
"telemetry" [color=lightblue];
"web" [color=lightblue];

"proxy" [color=orange];

"cli" -> "public-api";
"web" -> "public-api";

"destination" -> "kubernetes";

"proxy" -> "proxy-api";

"proxy-api" -> "destination";
"proxy-api" -> "telemetry";

"public-api" -> "tap";
"public-api" -> "telemetry";

"tap" -> "kubernetes";
"tap" -> "proxy";

"telemetry" -> "kubernetes";
"telemetry" -> "prometheus";

} conduit_components

Development configurations

Depending on use case, there are several configurations with which to develop and run Conduit:

  • Comprehensive: Integrated configuration using Minikube, most closely matches release.
  • Go: Development of the Go components using Docker Compose.
  • Web: Development of the Conduit Dashboard.
  • Rust: Standalone development of the Rust proxy.

Comprehensive

This configuration builds all Conduit components in Docker images, and deploys them onto Minikube. This configuration also builds and installs a conduit executable onto the local system. This setup most closely parallels our recommended production installation, documented at https://conduit.io/getting-started/.

These commands assume working Go and Minikube environments.

# ensure all go dependencies are in vendor
dep ensure && dep prune

# verify cluster (minikube) status
bin/go-run cli check

# build all docker images, using minikube as our docker repo
DOCKER_FORCE_BUILD=1 DOCKER_TRACE=1 bin/mkube bin/docker-build latest

# install conduit
bin/go-run cli install --version latest | kubectl apply -f -

# verify cli and server versions
bin/go-run cli version

# validate installation
kubectl --namespace=conduit get all

# view conduit dashboard
bin/go-run cli dashboard

# install the demo app
curl https://raw.githubusercontent.com/runconduit/conduit-examples/master/emojivoto/emojivoto.yml | conduit inject - --skip-inbound-ports=80 | kubectl apply -f -

# view demo app
minikube -n emojivoto service web-svc --url

# view details per deployment
bin/go-run cli stat deployments

# view a live pipeline of requests
bin/go-run cli tap deploy emojivoto/voting-svc

Go

These commands assume working Go and Docker environments.

To run all of the Go apps in a docker-compose environment:

docker-compose build
docker-compose up -d

# view dashboard
open http://$DOCKER_IP:8084

If your system is configured to talk to a Kubernetes cluster, you can simulate traffic to the docker-compose environment:

# confirm you are connected to Kubernetes
kubectl version

# simulate traffic
bin/go-run controller/script/simulate-proxy --kubeconfig ~/.kube/config --addr $DOCKER_IP:8086 --max-pods 10 --sleep 10ms

Testing

dep ensure && dep prune
go test ./...
go vet ./...

A note about Go run

Our instructions use a bin/go-run script in lieu go run. This is a convenience script that leverages caching via go build to make your build/run/debug loop faster.

In general, replace commands like this:

go run web/main.go

with this:

bin/go-run web

You may also leverage go-run to execute our conduit cli command. While in a release context you may run:

conduit check

In development you can run:

bin/go-run cli check

Web

This is a React app fronting a Go process. It uses webpack to bundle assets, and postcss to transform css.

These commands assume working Go and Yarn environments.

First time setup

Install Yarn and use it to install dependencies:

brew install yarn
cd web/app
yarn

Run web standalone

cd web/app
yarn && yarn webpack
cd ../..
bin/go-run web

The web server will be running on localhost:8084.

Note the web process depends on a public-api server, for which you have three options:

1. Connect to public-api locally

bin/go-run controller/cmd/public-api

2. Connect to public-api in docker-compose

Stop the web service, then run it locally and set the --api-addr flag to the address of the public API server that's running in your docker environment:

docker-compose stop web
bin/go-run web --api-addr=$DOCKER_IP:8085

3. Connect to public-api in Kubernetes

If you are running the public API server in Kubernetes, forward localhost:8085 to the Conduit controller pod:

POD_NAME=$(kubectl --namespace=conduit get po --selector=app=controller -o jsonpath='{.items[*].metadata.name}')
kubectl -n conduit port-forward $POD_NAME 8085:8085

Then connect the local web process to the forwarded port:

bin/go-run web --api-addr=localhost:8085

Webpack dev server

To develop with a webpack dev server, start the server in a separate window:

cd web/app
yarn webpack-dev-server

And then set the --webpack-dev-server flag when running the web server:

bin/go-run web --webpack-dev-server=http://localhost:8080

To add a JS dependency:

cd web/app
yarn add [dep]

Testing

cd web/app
yarn && yarn webpack
yarn karma start --single-run

Rust

These commands assume a working Rust environment.

To build and run the Rust proxy:

cargo build -p conduit-proxy
CONDUIT_PROXY_LOG=trace \
  CONDUIT_PROXY_PUBLIC_LISTENER=tcp://0:5432 \
  CONDUIT_PROXY_PRIVATE_FORWARD=tcp://127.0.0.1:1234 \
  CONDUIT_PROXY_CONTROL_URL=tcp://127.0.0.1:8086 \
  target/debug/conduit-proxy

To connect to a live proxy-api at localhost:8086:

bin/go-run controller/cmd/proxy-api

Testing

cargo check
cargo test

Dependencies

Updating protobuf dependencies

If you make Protobuf changes, run:

bin/protoc-go.sh

Updating Docker dependencies

The Rust proxy and Go Docker images rely on base dependency images with hard-coded SHA's:

gcr.io/runconduit/go-deps depends on

gcr.io/runconduit/proxy-deps depends on

The bin/update-proxy-deps-shas and bin/update-go-deps-shas must be run when their respective dependencies change.

Build Architecture

Build Architecture

build_architecture digraph G { rankdir=LR;
"Dockerfile-base" [color=lightblue, style=filled, shape=rect];
"Dockerfile-go-deps" [color=lightblue, style=filled, shape=rect];
"controller/Dockerfile" [color=lightblue, style=filled, shape=rect];
"cli/Dockerfile" [color=lightblue, style=filled, shape=rect];
"cli/Dockerfile-bin" [color=lightblue, style=filled, shape=rect];
"proxy/Dockerfile" [color=lightblue, style=filled, shape=rect];
"proxy/Dockerfile-deps" [color=lightblue, style=filled, shape=rect];
"proxy-init/Dockerfile" [color=lightblue, style=filled, shape=rect];
"proxy-init/integration-test/iptables/Dockerfile-tester" [color=lightblue, style=filled, shape=rect];
"web/Dockerfile" [color=lightblue, style=filled, shape=rect];

"proxy-init/integration-test/run_tests.sh" -> "proxy-init/integration-test/iptables/Dockerfile-tester";

"_docker.sh" -> "_log.sh";

"_gcp.sh";
"_log.sh";
"_tag.sh";

"docker-build" -> "_docker.sh";
"docker-build" -> "_tag.sh";

"docker-build" -> "docker-build-controller";
"docker-build" -> "docker-build-web";
"docker-build" -> "docker-build-proxy";
"docker-build" -> "docker-build-proxy-init";
"docker-build" -> "docker-build-cli";

"docker-build-base" -> "_docker.sh";
"docker-build-base" -> "Dockerfile-base";

"docker-build-cli" -> "_docker.sh";
"docker-build-cli" -> "_tag.sh";
"docker-build-cli" -> "docker-build-cli-bin";
"docker-build-cli" -> "cli/Dockerfile";

"docker-build-cli-bin" -> "_docker.sh";
"docker-build-cli-bin" -> "_tag.sh";
"docker-build-cli-bin" -> "docker-build-base";
"docker-build-cli-bin" -> "docker-build-go-deps";
"docker-build-cli-bin" -> "cli/Dockerfile-bin";

"docker-build-controller" -> "_docker.sh";
"docker-build-controller" -> "_tag.sh";
"docker-build-controller" -> "docker-build-base";
"docker-build-controller" -> "docker-build-go-deps";
"docker-build-controller" -> "controller/Dockerfile";

"docker-build-go-deps" -> "_docker.sh";
"docker-build-go-deps" -> "_tag.sh";
"docker-build-go-deps" -> "Dockerfile-go-deps";

"docker-build-proxy" -> "_docker.sh";
"docker-build-proxy" -> "_tag.sh";
"docker-build-proxy" -> "docker-build-base";
"docker-build-proxy" -> "docker-build-go-deps";
"docker-build-proxy" -> "proxy/Dockerfile";

"docker-build-proxy-deps" -> "_docker.sh";
"docker-build-proxy-deps" -> "_tag.sh";
"docker-build-proxy-deps" -> "proxy/Dockerfile-deps";

"docker-build-proxy-init" -> "_docker.sh";
"docker-build-proxy-init" -> "_tag.sh";
"docker-build-proxy-init" -> "docker-build-base";
"docker-build-proxy-init" -> "docker-build-go-deps";
"docker-build-proxy-init" -> "proxy-init/Dockerfile";

"docker-build-web" -> "_docker.sh";
"docker-build-web" -> "_tag.sh";
"docker-build-web" -> "docker-build-base";
"docker-build-web" -> "docker-build-go-deps";
"docker-build-web" -> "web/Dockerfile";

"docker-images" -> "_docker.sh";
"docker-images" -> "_tag.sh";

"docker-pull" -> "_docker.sh";
"docker-pull" -> "_tag.sh";

"docker-pull-deps" -> "_docker.sh";
"docker-pull-deps" -> "_tag.sh";

"docker-push" -> "_docker.sh";
"docker-push" -> "_tag.sh";

"docker-push-deps" -> "_docker.sh";
"docker-push-deps" -> "_tag.sh";

"docker-retag-all" -> "_docker.sh";

"go-run" -> ".gorun";

"minikube-start-hyperv.bat";

"mkube";

"protoc" -> ".protoc";

"protoc-go.sh" -> "protoc";

"root-tag" -> "_tag.sh";

"travis.yml" -> "_gcp.sh";
"travis.yml" -> "_tag.sh";
"travis.yml" -> "docker-build";
"travis.yml" -> "docker-pull-deps";
"travis.yml" -> "docker-push";
"travis.yml" -> "docker-push-deps";
"travis.yml" -> "docker-retag-all";
"travis.yml" -> "protoc-go.sh";

} build_architecture