Helloworld example with docker and linkerd

Hello,

I’m going through the helloworld linkerd docker examples readme and I can’t seem to find out how to get the services talking to linkerd.

I believe I need to bring up the linkerd docker container with a specific config, and then have the helloworld-client send the request to it but I’m not having much luck finding information on that configuration.

Thanks!

Hey @tfmertz! Good question. The helloworld docker image in that directory is primarily intended to be used as part of the series of examples that we have for deploying linkerd to Kubernetes. I’d recommend starting there if you’re going to be running in Kubernetes.

You can certainly build the helloworld image from that directory and run it in a docker environment. You would also need to run the buoyantio/linkerd docker image in the same environment, and then configure the “hello” service to talk to the “world” service via linkerd. You can do this by using the http_proxy environment variable, as described in the HTTP proxy integration feature page.

If you’d like to see a more fully featured docker setup that uses linkerd and some basic go apps, I’d recommend checking out the Add Steps demo in that repo. That has a docker-compose env that spins up linkerd and some backends, and sends some traffic through it.

Thanks for the quick reply, this is very helpful!

I am trying to get a quick and dirty gRPC routing up to test a proof of concept for decoupling one of our applications that is made up off 8 different services.

Would http proxy work in the case of gRPC? If so it’d might be a quick way to test.

I was able to get the docker image up in the helloworld directory, but I just used the plaintext gRPC configuration I found in the linkerd configuration instructions by running:

docker run -v "$PWD/linkerd.yaml":/io.buoyant/linkerd.yaml -p 4142:4142 -p 9990:9990 <image_id> /io.buoyant/linkerd.yaml

Would that plain text gRPC config work with the http_proxy?

Ah, gRPC – unfortunately http_proxy only really works for HTTP/1 requests. For gRPC, you can just configure your gRPC client to point directly at the linkerd port, and setup linkerd to route accordingly.

For a quick test with docker, try this:

Create a config.yml file:

routers:
- protocol: h2
  experimental: true
  dtab: /svc => /$/inet/127.1/7777;
  servers:
  - port: 4140
    ip: 0.0.0.0

Start a hello docker container serving gRPC:

$ docker run -d --net host buoyantio/helloworld:0.1.2 -protocol grpc

Start a linkerd docker container with the config.yml file:

$ docker run -d --net host -p 4140:4140 -p 9990:9990 -v `pwd`/config.yml:/config.yml buoyantio/linkerd:1.1.0 /config.yml

And send a request to linkerd with the helloworld-client script:

$ docker run --rm --net host --entrypoint=helloworld-client buoyantio/helloworld:0.1.2 localhost:4140
Hello!

You can also view linkerd’s admin dashboard on port 9990 of your docker host.

1 Like

I’m very close! Thanks for the example, that got me connecting the client to linkerd. But when the request hits linkerd it gives a NoBrokersAvailableException. I did some reading and it looks like the --net host flag doesn’t like Mac OS. So I threw it into a quick docker compose and I’m still getting this error:

linkerd_1  | I 0703 16:37:14.360 UTC THREAD29: no available endpoints
linkerd_1  | com.twitter.finagle.NoBrokersAvailableException: No hosts are available for /svc/helloworld_linkerd_1, Dtab.base=[/svc=>/#/inet/hello/7777], Dtab.local=[]. Remote Info: Not Available
linkerd_1  | 
linkerd_1  | E 0703 16:37:14.464 UTC THREAD29: [S L:/172.17.0.3:4140 R:/172.17.0.4:53262] dispatcher failed
linkerd_1  | com.twitter.finagle.ChannelClosedException: Connection reset by peer at remote address: /172.17.0.4:53262. Remote Info: Not Available

My docker-compose.yml file:

linkerd:
  image: buoyantio/linkerd
  volumes:
  - ./config.yml:/config.yml:rw
  links:
  - hello
  ports:
  - 4140:4140
  - 9990:9990
  command: /config.yml
hello:
  image: buoyantio/helloworld
  expose:
  - 7777
  command: -protocol grpc

I updated my config.yml to be:

routers:
- protocol: h2
  experimental: true
  label: grpc
  servers:
  - port: 4140
    ip: 0.0.0.0

  dtab: /svc => /#/inet/hello/7777;

The dtab now points to the hello container.

I’m using the client to connect by running:

docker run --rm --link helloworld_linkerd_1 --entrypoint=helloworld-client d602895688e0 helloworld_linkerd_1:4140

Dtabs are very new to me so I’m hoping I just have some of the configurations mixed up.

Hi @tfmertz!

That error message says your dtab is /svc=>/#/inet.hello/7777, but the config.yml you pasted says /svc => /#/inet/hello/7777. Is there a typo somewhere?

In the future, you can use $LINKERD_URL:9990/delegator to diagnose dtab misconfigurations.
See https://discourse.linkerd.io/t/debugging-a-linkerd-setup

1 Like

Thanks esbie, good catch. Yes, I fixed the typo but didn’t update the error output. I still get the same error with the dtab reading /svc => /#/inet/hello/7777;. Also, good tip with the delegator path.

I followed your link and have tested that the hello server is running at 7777 and that linkerd is running. However, it doesn’t seem like the requests are causing errors in the linkerd admin.

The client error I’m receiving is:

rpc error: code = Unavailable desc = stream terminated by RST_STREAM with error code: 7

So I think my dtab knowledge is to blame. I read through the linkerd dtab docs and found a section describing using /$/inet so I tried that instead of /#/inet and it seemed to work.

I don’t know the differences between the two, I guess inet isn’t a namer? Anyway, it seems to be working.

Oh! Yeah that’s a bit confusing. Generally the namers with /$/ prefix are from finagle, whereas the /#/ prefix is from linkerd namers (not a great distinction, I know).

Anyways I’m glad you were able to get it working!

Good to know! Thanks for helping me whittle down the possibilities. :slight_smile:

@tfmertz Glad to hear you got the dtab straightened out. Does this mean you have gRPC working end-to-end with linkerd in your docker-compose env?

@klingerf Yes, it is working now. I can docker compose up then run:

docker run --entrypoint=helloworld-client --link="helloworld_linkerd_1" buoyantio/helloworld helloworld_linkerd_1:4140

and get Hello world!! as the output.

But the Hello service is connecting directly to the world service. For my use case I was wondering if it is possible to have the client send a request to linkerd, which then routes it to hello, which then sends a request to linkerd, which then routes it to world and then displays “Hello World!!” Something like:

client -> linkerd -> hello -> linkerd -> world -> "Hello world!!"

But I believe that would take Go code changes and some additional dtab configuration.

Here is the docker-compose.yml and linkerd config.yml if you’re interested.

Edit: I added both YAML files into the /docker/helloworld directory of the linkerd examples project on my local.

docker-compose.yml

linkerd:
  image: buoyantio/linkerd
  volumes:
  - ./config.yml:/config.yml:rw
  links:
  - hello
  ports:
  - "4140:4140"
  - "9990:9990"
  command: /config.yml
hello:
  image: buoyantio/helloworld
  links:
  - world
  ports:
  - "7777:7777"
  command: -target world:7778 -protocol grpc
world:
  image: buoyantio/helloworld
  ports:
  - "7778:7778"
  command: -addr :7778 -text world -protocol grpc

config.yml

routers:
- protocol: h2
  experimental: true
  label: grpc
  servers:
  - port: 4140
    ip: 0.0.0.0

  dtab: |
    /svc => /$/inet/hello/7777;

Edit 2: Not sure if it’s worth noting, but I am having the issue outlined here

Hey @tfmertz, I’d recommend using docker-compose version 2 or later, since it has much better support for container networking, which comes into play here.

Here’s your docker-compose.yml file converted to v2:

version: "2"

services:
  linkerd:
    image: buoyantio/linkerd:1.1.0
    volumes:
    - ./config.yml:/config.yml:rw
    ports:
    - "4140:4140"
    - "9990:9990"
    command: /config.yml
  hello:
    image: buoyantio/helloworld:0.1.4
    ports:
    - "7777:7777"
    command: -target linkerd:4140 -protocol grpc
  world:
    image: buoyantio/helloworld:0.1.4
    ports:
    - "7778:7778"
    command: -addr :7778 -text world -protocol grpc

And to route to multiple gRPC services from the same linkerd router, you can adjust your config.yml as follows:

diff --git a/config.yml b/config.yml
index 1c2d26e..3092b22 100644
--- a/config.yml
+++ b/config.yml
@@ -6,5 +6,9 @@ routers:
   - port: 4140
     ip: 0.0.0.0
 
+  identifier:
+    kind: io.l5d.header.path
+    segments: 1
   dtab: |
-    /svc => /$/inet/hello/7777;
+    /svc/helloworld.Hello => /$/inet/hello/7777;
+    /svc/helloworld.World => /$/inet/world/7778;

That allows you to route based on the gRPC path that’s included as part of the request, and differentiate your traffic accordingly.

I’ve also adjust the -target flag for the hello service in your docker-compose file to point to linkerd:4140 instead of world:7777, which means it will route its request to the world service through linkerd first.

With these changes in place, you can use the helloworld-client script to send a request to linkerd port 4140, which will forward to the hello service, which calls the world service by way of linkerd port 4140. If it’s all working as expected, you should get:

$ helloworld-client 192.168.99.100:4140
Hello world!!

Note, for ease of use, if you have a working go environment, you can go get the helloworld-client script and install it locally with:

$ go get -u github.com/linkerd/linkerd-examples/docker/helloworld/helloworld-client
$ helloworld-client -h
Usage: helloworld-client <host>:<port> [flags]
  -streaming
    	send streaming requests

Hope that helps!

I was able to get that running no problem! Thank you. I had read that there was a way to route based on the gRPC path, but hadn’t quite pieced it together. That is going to be very helpful. :+1: