Skip to content

Add configuration file and runtime management of NGINX #77

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 6 commits into from
Mar 31, 2022
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
17 changes: 14 additions & 3 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,25 @@ RUN go mod download
COPY cmd /go/src/github.com/nginxinc/nginx-gateway-kubernetes/cmd
COPY internal /go/src/github.com/nginxinc/nginx-gateway-kubernetes/internal
COPY pkg /go/src/github.com/nginxinc/nginx-gateway-kubernetes/pkg
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -a -ldflags "-s -w -X main.version=${VERSION} -X main.commit=${GIT_COMMIT} -X main.date=${DATE}" -o ./build/.out/gateway .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -a -ldflags "-s -w -X main.version=${VERSION} -X main.commit=${GIT_COMMIT} -X main.date=${DATE}" -o gateway .

FROM alpine:3.15 as capabilizer
RUN apk add --no-cache libcap

FROM capabilizer as local-capabilizer
COPY ./build/.out/gateway /usr/bin/
RUN setcap 'cap_kill=+ep' /usr/bin/gateway

FROM capabilizer as container-capabilizer
COPY --from=builder /go/src/github.com/nginxinc/nginx-gateway-kubernetes/cmd/gateway/gateway /usr/bin/
RUN setcap 'cap_kill=+ep' /usr/bin/gateway

FROM scratch as common
USER 1001:1001
ENTRYPOINT [ "/usr/bin/gateway" ]

FROM common as container
COPY --from=builder /go/src/github.com/nginxinc/nginx-gateway-kubernetes/cmd/gateway/gateway /usr/bin/
COPY --from=container-capabilizer /usr/bin/gateway /usr/bin/

FROM common as local
COPY ./build/.out/gateway /usr/bin/
COPY --from=local-capabilizer /usr/bin/gateway /usr/bin/
13 changes: 11 additions & 2 deletions deploy/manifests/nginx-gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,30 @@ spec:
labels:
app: nginx-gateway
spec:
shareProcessNamespace: true
serviceAccountName: nginx-gateway
volumes:
- name: nginx-config
emptyDir: { }
initContainers:
- image: busybox:1.34
- image: busybox:1.34 # FIXME(pleshakov): use gateway container to init the Config with proper main config
name: nginx-config-initializer
command: [ 'sh', '-c', 'echo "events {} http { server { default_type text/plain; return 200 \"hello from \$hostname\n\"; } }" > /etc/nginx/nginx.conf' ]
command: [ 'sh', '-c', 'echo "events {} pid /etc/nginx/nginx.pid; http { include /etc/nginx/conf.d/*.conf; server { default_type text/html; return 404; } }" > /etc/nginx/nginx.conf && mkdir /etc/nginx/conf.d && chown 1001:0 /etc/nginx/conf.d' ]
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx
containers:
- image: nginx-gateway:0.0.1
imagePullPolicy: IfNotPresent
name: nginx-gateway
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx
securityContext:
runAsUser: 1001
# FIXME(pleshakov) - figure out which capabilities are required
# dropping ALL and adding only CAP_KILL doesn't work
# Note: CAP_KILL is needed for sending HUP signal to NGINX main process
args:
- --gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway/gateway
- image: nginx:1.21.3
Expand Down
60 changes: 42 additions & 18 deletions internal/events/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

"github.com/go-logr/logr"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/config"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/file"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/runtime"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status"
apiv1 "k8s.io/api/core/v1"
Expand All @@ -14,12 +16,14 @@ import (

// EventLoop is the main event loop of the Gateway.
type EventLoop struct {
conf state.Configuration
serviceStore state.ServiceStore
generator config.Generator
eventCh <-chan interface{}
logger logr.Logger
statusUpdater status.Updater
conf state.Configuration
serviceStore state.ServiceStore
generator config.Generator
eventCh <-chan interface{}
logger logr.Logger
statusUpdater status.Updater
nginxFileMgr file.Manager
nginxRuntimeMgr runtime.Manager
}

// NewEventLoop creates a new EventLoop.
Expand All @@ -30,14 +34,18 @@ func NewEventLoop(
eventCh <-chan interface{},
statusUpdater status.Updater,
logger logr.Logger,
nginxFileMgr file.Manager,
nginxRuntimeMgr runtime.Manager,
) *EventLoop {
return &EventLoop{
conf: conf,
serviceStore: serviceStore,
generator: generator,
eventCh: eventCh,
statusUpdater: statusUpdater,
logger: logger.WithName("eventLoop"),
conf: conf,
serviceStore: serviceStore,
generator: generator,
eventCh: eventCh,
statusUpdater: statusUpdater,
logger: logger.WithName("eventLoop"),
nginxFileMgr: nginxFileMgr,
nginxRuntimeMgr: nginxRuntimeMgr,
}
}

Expand Down Expand Up @@ -118,14 +126,8 @@ func (el *EventLoop) processChangesAndStatusUpdates(ctx context.Context, changes
el.logger.Info("Processing a change",
"host", c.Host.Value)

// TO-DO: This code is temporary. We will remove it once we have a component that processes changes.
fmt.Printf("%+v\n", c)

if c.Op == state.Upsert {
cfg, warnings := el.generator.GenerateForHost(c.Host)
// TO-DO: for now, we only print the generated config, without writing it on the file system
// and reloading NGINX.
fmt.Println(string(cfg))

for obj, objWarnings := range warnings {
for _, w := range objWarnings {
Expand All @@ -137,6 +139,28 @@ func (el *EventLoop) processChangesAndStatusUpdates(ctx context.Context, changes
"warning", w)
}
}

el.logger.Info("Writing configuration",
"host", c.Host.Value)

err := el.nginxFileMgr.WriteServerConfig(c.Host.Value, cfg)
if err != nil {
el.logger.Error(err, "Failed to write configuration",
"host", c.Host.Value)
}
} else {
err := el.nginxFileMgr.DeleteServerConfig(c.Host.Value)
if err != nil {
el.logger.Error(err, "Failed to delete configuration",
"host", c.Host.Value)
}
}
}

if len(changes) > 0 {
err := el.nginxRuntimeMgr.Reload(ctx)
if err != nil {
el.logger.Error(err, "Failed to reload NGINX")
}
}

Expand Down
90 changes: 82 additions & 8 deletions internal/events/loop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import (
"context"

"github.com/nginxinc/nginx-gateway-kubernetes/internal/events"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/config"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/config/configfakes"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/file/filefakes"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/runtime/runtimefakes"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state/statefakes"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status/statusfakes"
Expand Down Expand Up @@ -37,6 +40,8 @@ var _ = Describe("EventLoop", func() {
var fakeUpdater *statusfakes.FakeUpdater
var fakeServiceStore *statefakes.FakeServiceStore
var fakeGenerator *configfakes.FakeGenerator
var fakeNginxFimeMgr *filefakes.FakeManager
var fakeNginxRuntimeMgr *runtimefakes.FakeManager
var cancel context.CancelFunc
var eventCh chan interface{}
var errorCh chan error
Expand All @@ -47,7 +52,9 @@ var _ = Describe("EventLoop", func() {
fakeUpdater = &statusfakes.FakeUpdater{}
fakeServiceStore = &statefakes.FakeServiceStore{}
fakeGenerator = &configfakes.FakeGenerator{}
ctrl = events.NewEventLoop(fakeConf, fakeServiceStore, fakeGenerator, eventCh, fakeUpdater, zap.New())
fakeNginxFimeMgr = &filefakes.FakeManager{}
fakeNginxRuntimeMgr = &runtimefakes.FakeManager{}
ctrl = events.NewEventLoop(fakeConf, fakeServiceStore, fakeGenerator, eventCh, fakeUpdater, zap.New(), fakeNginxFimeMgr, fakeNginxRuntimeMgr)

var ctx context.Context

Expand All @@ -71,8 +78,10 @@ var _ = Describe("EventLoop", func() {
It("should process upsert event", func() {
fakeChanges := []state.Change{
{
Op: state.Upsert,
Host: state.Host{},
Op: state.Upsert,
Host: state.Host{
Value: "example.com",
},
},
}
fakeStatusUpdates := []state.StatusUpdate{
Expand All @@ -83,6 +92,9 @@ var _ = Describe("EventLoop", func() {
}
fakeConf.UpsertHTTPRouteReturns(fakeChanges, fakeStatusUpdates)

fakeCfg := []byte("fake")
fakeGenerator.GenerateForHostReturns(fakeCfg, config.Warnings{})

hr := &v1alpha2.HTTPRoute{}

eventCh <- &events.UpsertEvent{
Expand All @@ -102,13 +114,22 @@ var _ = Describe("EventLoop", func() {

Eventually(fakeGenerator.GenerateForHostCallCount).Should(Equal(1))
Expect(fakeGenerator.GenerateForHostArgsForCall(0)).Should(Equal(fakeChanges[0].Host))

Eventually(fakeNginxFimeMgr.WriteServerConfigCallCount).Should(Equal(1))
host, cfg := fakeNginxFimeMgr.WriteServerConfigArgsForCall(0)
Expect(host).Should(Equal("example.com"))
Expect(cfg).Should(Equal(fakeCfg))

Eventually(fakeNginxRuntimeMgr.ReloadCallCount).Should(Equal(1))
})

It("should process delete event", func() {
fakeChanges := []state.Change{
{
Op: state.Delete,
Host: state.Host{},
Op: state.Delete,
Host: state.Host{
Value: "example.com",
},
},
}
fakeStatusUpdates := []state.StatusUpdate{
Expand Down Expand Up @@ -137,9 +158,10 @@ var _ = Describe("EventLoop", func() {
return updates
}).Should(Equal(fakeStatusUpdates))

// TO-DO:
// once we have a component that processes host deletion, ensure that
// its corresponding method is eventually called
Eventually(fakeNginxFimeMgr.DeleteServerConfigCallCount).Should(Equal(1))
Expect(fakeNginxFimeMgr.DeleteServerConfigArgsForCall(0)).Should(Equal("example.com"))

Eventually(fakeNginxRuntimeMgr.ReloadCallCount).Should(Equal(1))
})
})

Expand Down Expand Up @@ -180,6 +202,58 @@ var _ = Describe("EventLoop", func() {
})
})

Describe("Processing events common cases", func() {
AfterEach(func() {
cancel()

var err error
Eventually(errorCh).Should(Receive(&err))
Expect(err).To(BeNil())
})

It("should reload once in case of multiple changes", func() {
fakeChanges := []state.Change{
{
Op: state.Delete,
Host: state.Host{
Value: "one.example.com",
},
},
{
Op: state.Upsert,
Host: state.Host{
Value: "two.example.com",
},
},
}
fakeConf.DeleteHTTPRouteReturns(fakeChanges, nil)

fakeCfg := []byte("fake")
fakeGenerator.GenerateForHostReturns(fakeCfg, config.Warnings{})

nsname := types.NamespacedName{Namespace: "test", Name: "route"}

// the exact event doesn't matter. what matters is the generated changes return by DeleteHTTPRouteReturns
eventCh <- &events.DeleteEvent{
NamespacedName: nsname,
Type: &v1alpha2.HTTPRoute{},
}

Eventually(fakeConf.DeleteHTTPRouteCallCount).Should(Equal(1))
Expect(fakeConf.DeleteHTTPRouteArgsForCall(0)).Should(Equal(nsname))

Eventually(fakeNginxFimeMgr.WriteServerConfigCallCount).Should(Equal(1))
host, cfg := fakeNginxFimeMgr.WriteServerConfigArgsForCall(0)
Expect(host).Should(Equal("two.example.com"))
Expect(cfg).Should(Equal(fakeCfg))

Eventually(fakeNginxFimeMgr.DeleteServerConfigCallCount).Should(Equal(1))
Expect(fakeNginxFimeMgr.DeleteServerConfigArgsForCall(0)).Should(Equal("one.example.com"))

Eventually(fakeNginxRuntimeMgr.ReloadCallCount).Should(Equal(1))
})
})

Describe("Edge cases", func() {
AfterEach(func() {
cancel()
Expand Down
6 changes: 5 additions & 1 deletion internal/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
hr "github.com/nginxinc/nginx-gateway-kubernetes/internal/implementations/httproute"
svc "github.com/nginxinc/nginx-gateway-kubernetes/internal/implementations/service"
ngxcfg "github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/config"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/file"
ngxruntime "github.com/nginxinc/nginx-gateway-kubernetes/internal/nginx/runtime"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status"
nginxgwv1alpha1 "github.com/nginxinc/nginx-gateway-kubernetes/pkg/apis/gateway/v1alpha1"
Expand Down Expand Up @@ -78,7 +80,9 @@ func Start(cfg config.Config) error {
serviceStore := state.NewServiceStore()
reporter := status.NewUpdater(mgr.GetClient(), cfg.Logger)
configGenerator := ngxcfg.NewGeneratorImpl(serviceStore)
eventLoop := events.NewEventLoop(conf, serviceStore, configGenerator, eventCh, reporter, cfg.Logger)
nginxFileMgr := file.NewManagerImpl()
nginxRuntimeMgr := ngxruntime.NewManagerImpl()
eventLoop := events.NewEventLoop(conf, serviceStore, configGenerator, eventCh, reporter, cfg.Logger, nginxFileMgr, nginxRuntimeMgr)

err = mgr.Add(eventLoop)
if err != nil {
Expand Down
Loading