#nginx as a service

1 messages · Page 1 of 1 (latest)

eager iris
#

Is there something special about nginx-latest that makes it not able to be run in the background as a service? A minimal reproducible example taken from modifying the existing service example demonstrates that we end up with blocking behavior. Am I missing something here?

import sys

import anyio
import dagger


async def main():
    # create Dagger client
    async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
        # create HTTP service container with exposed port 8080
        http_srv = (
            client.container()
            .from_("nginx:latest")
            .with_exposed_port(8080)
        )

        # create client container with service binding
        # access HTTP service and print result
        val = await (
            client.container()
            .from_("alpine")
            .with_service_binding("www", http_srv)
            .with_exec(["wget", "-O-", "http://www:8080"])
            .stdout()
        )

    print(val)


anyio.run(main)
torn ivy
#

at first glance this should work… does replacing nginx latest with another service fix the issue?

eager iris
#

the original example provided does in fact work

torn ivy
#

Also I guess that image is expected to listen on port 8080 in its default configuration?

eager iris
#

e.g. this works:

import sys

import anyio
import dagger


async def main():
    # create Dagger client
    async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
        # create HTTP service container with exposed port 8080
        http_srv = (
            client.container()
            .from_("python")
            .with_directory(
                "/srv",
                client.directory().with_new_file("index.html", "Hello, world!"),
            )
            .with_workdir("/srv")
            .with_exec(["python", "-m", "http.server", "8080"])
            .with_exposed_port(8080)
        )

        # create client container with service binding
        # access HTTP service and print result
        val = await (
            client.container()
            .from_("alpine")
            .with_service_binding("www", http_srv)
            .with_exec(["wget", "-O-", "http://www:8080"])
            .stdout()
        )

    print(val)

def run():
    anyio.run(main)

if __name__ == "__main__":
    run()

(from the dagger cookbook)

#

I'm not actually sure, but we never actually get to the point where we find out, it blocks on spinning up nginx, which successfully spins up, but never proceeds to the with_exec

#
13: exec /docker-entrypoint.sh nginx -g daemon off;
13:> in service P5SAG7SPJEJSI
13: [0.11s] /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
13: [0.11s] /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
13: [0.11s] /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
13: [0.12s] 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
13: [0.12s] 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
13: [0.12s] /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
13: [0.12s] /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
13: [0.13s] /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
13: [0.13s] /docker-entrypoint.sh: Configuration complete; ready for start up
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: using the "epoll" event method
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: nginx/1.25.1
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: built by gcc 12.2.0 (Debian 12.2.0-14) 
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: OS: Linux 5.15.49-linuxkit-pr
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: getrlimit(RLIMIT_NOFILE): 1048576:1048576
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker processes
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 36
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 37
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 38
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 39
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 40
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 41
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 42
13: [0.14s] 2023/07/17 02:14:16 [notice] 13#13: start worker process 43
torn ivy
#

no change to your version or configuration of dagger itself between working and broken snippets? just the code changed?

eager iris
#

nope, just the code changed

#

i'm on latest version of dagger btw, should just be able to save those two python files and run them side by side to repro

torn ivy
#

definitely seems nginx-related, but i’m not sure how

#

is there an option in WithServiceBinding to disable health checks?

#

one theory is that that image only listens to localhost in its default config, so port is not reachable, so healtcheck times out?

tepid holly
#

The default nginx port is 80 not 8080.

tepid holly
#
import sys

import anyio
import dagger


async def main():
    # create Dagger client
    async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
-        # create HTTP service container with exposed port 8080
+        # create HTTP service container with exposed port 80
        http_srv = (
            client.container()
            .from_("nginx:latest")
-            .with_exposed_port(8080)
+            .with_exposed_port(80)
        )

        # create client container with service binding
        # access HTTP service and print result
        val = await (
            client.container()
            .from_("alpine")
            .with_service_binding("www", http_srv)
-            .with_exec(["wget", "-O-", "http://www:8080"])
+            .with_exec(["wget", "-O-", "http://www"])
            .stdout()
        )

    print(val)


anyio.run(main)
torn ivy
#

@tepid holly ha ha that explains it 🙂

Btw isn’t that “with_exposed_port” optional, assuming the OCI config already exposes port 80?

tepid holly
#

Yes, it is. You can omit it.

#

However, if you have multiple ports, it'll use the first one. The nginx image exposes 80 and 443. Better to be explicit.

#

I mean dagger healtchecks. It'll use the first exposed in the image if you don't use with_exposed_port. However, the exposed ports in OCI is a map.

eager iris
#

yep. That fixed it. Can you help me understand why not using the default nginx port of 80 causes it to silently block without throwing any sort of error?

#

( I spent a lot of time scratching my head trying to figure this out yesterday, and some sort of thing indicating what you are saying would have saved me a lot of time)

#

from the end user perspective, all I see is "thing isn't continuing when it should. what"

tepid holly
#

For you to use port 8080 you need to tell nginx to use it.

eager iris
#

great, should we configure logging in the engine to show that it's at least attempting a healthcheck and at what port?

#

(or is that already configured?)

#

some sort of feedback knowing it was the healthcheck trying over and over is what would be nice, is what I'm getting at

tepid holly
#

With the TUI you can: dagger run --debug

#

\cc @median topaz

eager iris
#

if you agree, I can submit one 🙂

#

another question, does healthcheck have a timeout by default? It might have also been interesting to have something like 2-3 minutes and then have dagger bomb out and be like "healthcheck failed on port x"

#

(maybe 2-3 minutes is the wrong timeframe, but you get the idea)

tepid holly
tepid holly
eager iris
#

Ok. Would you rather me comment on your existing issue or submit a PR on the docs, or both?

tepid holly
#

If you can, PR on the docs 👍 and new issue for having a default timeout in healtchecks. I'll create an additional issue for extending the API with more advanced healtcheck controls so you can fine tune what you need (right now it's a comment in a merged PR).

eager iris
desert quarry
#

Hi, I am hacking the expose service to host tutorial to clone/build the dagger hello nodeJs app and serve it on an Nginx server.
Though, it seems I am having the same error of opening and exposing the exposed port (80). It seems the port is Healthy but not ready because of binding error 🤔

 @func()
  cloneAndServe(
    repository: string = "https://github.com/dagger/hello-dagger.git",
    ref: string = "refs/heads/main"
  ): Service {
    const repoDir = dag.git(repository).ref(ref).tree();

    const dist = dag
      .container()
      .from("node:latest")
      .withDirectory("/app", repoDir)
      .withWorkdir("/app")
      .withExec(["npm", "install"])
      .withExec(["npm", "run", "build"])
      .directory("/app/dist");

    return dag
      .container()
      .from("nginx:latest")
      .withDirectory("/usr/share/nginx/html", dist)
      .withExposedPort(80)
      .asService();
  }

Thanks for feeback 🙂

torn ivy
desert quarry
gleaming crystal
desert quarry
gleaming crystal
#

Since the Dagger client runs without elevated privileges, it can't bind to anything below 1025

desert quarry
#

Ah yes it should, but this time fails the HealthCheck right ?

gleaming crystal
#

So adding the --ports flag allows to change this behavior

desert quarry
# gleaming crystal So adding the --ports flag allows to change this behavior

It seems we can set the ports flag as opts for .up() method. Should it still work as following, with dagger call -E clone-and-serve command ?

return dag
  .container()
  .from("nginx:latest")
  .withDirectory("/usr/share/nginx/html", dist)
  .withExposedPort(80)
  .asService()
  .up({ ports: [{ backend: 80, frontend: 8080 }] });
torn ivy
desert quarry