Skip to content

Commit

Permalink
FastCGI backend support (kubernetes#2982)
Browse files Browse the repository at this point in the history
Co-authored-by: Pierrick Charron <pierrick@adoy.net>
  • Loading branch information
cdemers and adoy committed Jul 31, 2019
1 parent 91a68ff commit 72271e9
Show file tree
Hide file tree
Showing 22 changed files with 938 additions and 6 deletions.
117 changes: 117 additions & 0 deletions docs/user-guide/fcgi-services.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@


# Exposing FastCGI Servers

> **FastCGI** is a [binary protocol](https://en.wikipedia.org/wiki/Binary_protocol "Binary protocol") for interfacing interactive programs with a [web server](https://en.wikipedia.org/wiki/Web_server "Web server"). [...] (It's) aim is to reduce the overhead related to interfacing between web server and CGI programs, allowing a server to handle more web page requests per unit of time.
>
> &mdash; Wikipedia
The _ingress-nginx_ ingress controller can be used to directly expose [FastCGI](https://en.wikipedia.org/wiki/FastCGI) servers. Enabling FastCGI in your Ingress only requires setting the _backend-protocol_ annotation to `FCGI`, and with a couple more annotations you can customize the way _ingress-nginx_ handles the communication with your FastCGI _server_.


## Example Objects to Expose a FastCGI Pod

The _Pod_ example object below exposes port `9000`, which is the conventional FastCGI port.

```yaml
apiVersion: v1
kind: Pod
metadata:
name: example-app
labels:
app: example-app
spec:
containers:
- name: example-app
image: example-app:1.0
ports:
- containerPort: 9000
name: fastcgi
```
The _Service_ object example below matches port `9000` from the _Pod_ object above.

```yaml
apiVersion: v1
kind: Service
metadata:
name: example-service
spec:
selector:
app: example-app
ports:
- port: 9000
targetPort: 9000
name: fastcgi
```

And the _Ingress_ and _ConfigMap_ objects below demonstrates the supported _FastCGI_ specific annotations (NGINX actually has 50 FastCGI directives, all of which have not been exposed in the ingress yet), and matches the service `example-service`, and the port named `fastcgi` from above. The _ConfigMap_ **must** be created first for the _Ingress Controller_ to be able to find it when the _Ingress_ object is created, otherwise you will need to restart the _Ingress Controller_ pods.

```yaml
# The ConfigMap MUST be created first for the ingress controller to be able to
# find it when the Ingress object is created.
apiVersion: v1
kind: ConfigMap
metadata:
name: example-cm
data:
SCRIPT_FILENAME: "/example/index.php"
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
nginx.ingress.kubernetes.io/fastcgi-index: "index.php"
nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-cm"
name: example-app
spec:
rules:
- host: app.example.com
http:
paths:
- backend:
serviceName: example-service
servicePort: fastcgi
```

## The FastCGI Ingress Annotations

### The `nginx.ingress.kubernetes.io/backend-protocol` Annotation

To enable FastCGI, the `backend-protocol` annotation needs to be set to `FCGI`, which overrides the default `HTTP` value.

> `nginx.ingress.kubernetes.io/backend-protocol: "FCGI"`

This enables the _FastCGI_ mode for the whole _Ingress_ object.

### The `nginx.ingress.kubernetes.io/fastcgi-index` Annotation

To specify an index file, the `fastcgi-index` annotation value can optionally be set. In the example below, the value is set to `index.php`. This annotation corresponds to [the _NGINX_ `fastcgi_index` directive](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_index).

> `nginx.ingress.kubernetes.io/fastcgi-index: "index.php"`

### The `nginx.ingress.kubernetes.io/fastcgi-params-configmap` Annotation

To specify [_NGINX_ `fastcgi_param` directives](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_param), the `fastcgi-params-configmap` annotation is used, which in turn must lead to a _ConfigMap_ object containing the _NGINX_ `fastcgi_param` directives as key/values.

> `nginx.ingress.kubernetes.io/fastcgi-params: "example-configmap"`

And the _ConfigMap_ object to specify the `SCRIPT_FILENAME` and `HTTP_PROXY` _NGINX's_ `fastcgi_param` directives will look like the following:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: example-configmap
data:
SCRIPT_FILENAME: "/example/index.php"
HTTP_PROXY: ""
```
Using the _namespace/_ prefix is also supported, for example:

> `nginx.ingress.kubernetes.io/fastcgi-params: "example-namespace/example-configmap"`
105 changes: 105 additions & 0 deletions images/fastcgi-helloserver/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
all: all-container

BUILDTAGS=

# Use the 0.0 tag for testing, it shouldn't clobber any release builds
TAG?=0.1
REGISTRY?=kubernetes-ingress-controller
GOOS?=linux
DOCKER?=docker
SED_I?=sed -i
GOHOSTOS ?= $(shell go env GOHOSTOS)

PKG=k8s.io/ingress-nginx/images/fastcgi-helloserver

ifeq ($(GOHOSTOS),darwin)
SED_I=sed -i ''
endif

REPO_INFO=$(shell git config --get remote.origin.url)

ARCH ?= $(shell go env GOARCH)
GOARCH = ${ARCH}

BASEIMAGE?=alpine:3.9

ALL_ARCH = amd64 arm arm64 ppc64le

QEMUVERSION=v3.0.0

IMGNAME = fastcgi-helloserver
IMAGE = $(REGISTRY)/$(IMGNAME)
MULTI_ARCH_IMG = $(IMAGE)-$(ARCH)

ifeq ($(ARCH),arm)
QEMUARCH=arm
GOARCH=arm
endif
ifeq ($(ARCH),arm64)
QEMUARCH=aarch64
endif
ifeq ($(ARCH),ppc64le)
QEMUARCH=ppc64le
GOARCH=ppc64le
endif

TEMP_DIR := $(shell mktemp -d)

DOCKERFILE := $(TEMP_DIR)/rootfs/Dockerfile

sub-container-%:
$(MAKE) ARCH=$* build container

sub-push-%:
$(MAKE) ARCH=$* push

all-container: $(addprefix sub-container-,$(ALL_ARCH))

all-push: $(addprefix sub-push-,$(ALL_ARCH))

container: .container-$(ARCH)
.container-$(ARCH):
cp -r ./* $(TEMP_DIR)
$(SED_I) 's|BASEIMAGE|$(BASEIMAGE)|g' $(DOCKERFILE)
$(SED_I) "s|QEMUARCH|$(QEMUARCH)|g" $(DOCKERFILE)

ifeq ($(ARCH),amd64)
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
$(SED_I) "/CROSS_BUILD_/d" $(DOCKERFILE)
else
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
# $(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMUVERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(TEMP_DIR)/rootfs
$(SED_I) "s/CROSS_BUILD_//g" $(DOCKERFILE)
endif

$(DOCKER) build -t $(MULTI_ARCH_IMG):$(TAG) $(TEMP_DIR)/rootfs

ifeq ($(ARCH), amd64)
# This is for to maintain the backward compatibility
$(DOCKER) tag $(MULTI_ARCH_IMG):$(TAG) $(IMAGE):$(TAG)
endif

push: .push-$(ARCH)
.push-$(ARCH):
$(DOCKER) push $(MULTI_ARCH_IMG):$(TAG)
ifeq ($(ARCH), amd64)
$(DOCKER) push $(IMAGE):$(TAG)
endif

clean:
$(DOCKER) rmi -f $(MULTI_ARCH_IMG):$(TAG) || true

build: clean
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -a -installsuffix cgo \
-ldflags "-s -w" \
-o ${TEMP_DIR}/rootfs/fastcgi-helloserver ${PKG}/...

release: all-container all-push
echo "done"

.PHONY: register-qemu
register-qemu:
# Register /usr/bin/qemu-ARCH-static as the handler for binaries in multiple platforms
$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
30 changes: 30 additions & 0 deletions images/fastcgi-helloserver/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"fmt"
"net"
"net/http"
"net/http/fcgi"
)

func hello(w http.ResponseWriter, r *http.Request) {
keys, ok := r.URL.Query()["name"]

if !ok || len(keys[0]) < 1 {
fmt.Fprintf(w, "Hello world!")
return
}

key := keys[0]
fmt.Fprintf(w, "Hello "+string(key)+"!")
}

func main() {
http.HandleFunc("/hello", hello)

l, err := net.Listen("tcp", "0.0.0.0:9000")
if err != nil {
panic(err)
}
fcgi.Serve(l, nil)
}
21 changes: 21 additions & 0 deletions images/fastcgi-helloserver/rootfs/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2017 The Kubernetes Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM BASEIMAGE

CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/

COPY . /

CMD ["/fastcgi-helloserver"]
3 changes: 3 additions & 0 deletions internal/ingress/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
"k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors"
"k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend"
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
"k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload"
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
Expand Down Expand Up @@ -82,6 +83,7 @@ type Ingress struct {
CustomHTTPErrors []int
DefaultBackend *apiv1.Service
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
FastCGI fastcgi.Config
Denied *string
ExternalAuth authreq.Config
EnableGlobalAuth bool
Expand Down Expand Up @@ -128,6 +130,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"CorsConfig": cors.NewParser(cfg),
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
"DefaultBackend": defaultbackend.NewParser(cfg),
"FastCGI": fastcgi.NewParser(cfg),
"ExternalAuth": authreq.NewParser(cfg),
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
Expand Down
2 changes: 1 addition & 1 deletion internal/ingress/annotations/backendprotocol/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
const HTTP = "HTTP"

var (
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS)$`)
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
)

type backendProtocol struct {
Expand Down
Loading

0 comments on commit 72271e9

Please sign in to comment.