k8s: Use traefik as default ingress, added improved app url handling to support subpath deployments
This commit is contained in:
parent
f2c9e4a3d6
commit
20e389fccd
|
@ -311,8 +311,12 @@ deploy:
|
||||||
# CI_ENVIRONMENT_URL is the URL configured in the GitLab environment
|
# CI_ENVIRONMENT_URL is the URL configured in the GitLab environment
|
||||||
- export CI_ENVIRONMENT_URL="${CI_ENVIRONMENT_URL:-https://${CI_PROJECT_PATH_SLUG}.${KUBE_INGRESS_BASE_DOMAIN}/}"
|
- export CI_ENVIRONMENT_URL="${CI_ENVIRONMENT_URL:-https://${CI_PROJECT_PATH_SLUG}.${KUBE_INGRESS_BASE_DOMAIN}/}"
|
||||||
- export CI_IMAGE=$RELEASE_IMAGE
|
- export CI_IMAGE=$RELEASE_IMAGE
|
||||||
|
- export CI_INGRESS_CLASS=${CI_INGRESS_CLASS:-traefik}
|
||||||
|
- export CI_INGRESS_MATCH=${CI_INGRESS_MATCH:-$( if [[ "$CI_INGRESS_CLASS" == "nginx" ]]; then echo '/?(.*)'; fi )}
|
||||||
|
- export CI_INGRESS_TRAEFIK_ENTRYPOINT=${CI_INGRESS_TRAEFIK_ENTRYPOINT:-websecure}
|
||||||
- export CI_INGRESS_DOMAIN=$(echo "$CI_ENVIRONMENT_URL" | grep -oP '(?:https?://)?\K([^/]+)' | head -n1)
|
- export CI_INGRESS_DOMAIN=$(echo "$CI_ENVIRONMENT_URL" | grep -oP '(?:https?://)?\K([^/]+)' | head -n1)
|
||||||
- export CI_INGRESS_PATH=$(echo "$CI_ENVIRONMENT_URL" | grep -oP '(?:https?://)?(?:[^/])+\K(.*)')
|
- export CI_INGRESS_PATH=$(echo "$CI_ENVIRONMENT_URL" | grep -oP '(?:https?://)?(?:[^/])+\K(.*)')
|
||||||
|
- '[[ "${CI_INGRESS_PATH}" == /* ]] || export CI_INGRESS_PATH="/${CI_INGRESS_PATH}"'
|
||||||
- export CI_KUBE_NAMESPACE=$KUBE_NAMESPACE
|
- export CI_KUBE_NAMESPACE=$KUBE_NAMESPACE
|
||||||
# Any available storage class like default, local-path (if you know what you are doing ;), longhorn etc.
|
# Any available storage class like default, local-path (if you know what you are doing ;), longhorn etc.
|
||||||
- export CI_PVC_SC=${CI_PVC_SC:-"${CI_PVC_SC_LOCAL:-local-path}"}
|
- export CI_PVC_SC=${CI_PVC_SC:-"${CI_PVC_SC_LOCAL:-local-path}"}
|
||||||
|
@ -328,6 +332,7 @@ deploy:
|
||||||
done
|
done
|
||||||
|
|
||||||
- echo "Deploying to ${CI_ENVIRONMENT_URL}"
|
- echo "Deploying to ${CI_ENVIRONMENT_URL}"
|
||||||
|
- kubectl diff -f deployment.yaml || true
|
||||||
- kubectl apply -f deployment.yaml
|
- kubectl apply -f deployment.yaml
|
||||||
- >-
|
- >-
|
||||||
kubectl -n $CI_KUBE_NAMESPACE wait --for=condition=Ready pods --timeout=${CI_WAIT_TIMEOUT:-5}m
|
kubectl -n $CI_KUBE_NAMESPACE wait --for=condition=Ready pods --timeout=${CI_WAIT_TIMEOUT:-5}m
|
||||||
|
|
|
@ -66,7 +66,6 @@ metadata:
|
||||||
labels:
|
labels:
|
||||||
app: <CI_PROJECT_PATH_SLUG>
|
app: <CI_PROJECT_PATH_SLUG>
|
||||||
environment: <CI_ENVIRONMENT_SLUG>
|
environment: <CI_ENVIRONMENT_SLUG>
|
||||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
|
||||||
spec:
|
spec:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
ports:
|
ports:
|
||||||
|
@ -105,6 +104,7 @@ spec:
|
||||||
environment: <CI_ENVIRONMENT_SLUG>
|
environment: <CI_ENVIRONMENT_SLUG>
|
||||||
tier: application
|
tier: application
|
||||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
commit: '<CI_COMMIT_SHORT_SHA>'
|
||||||
|
pipeline: '<CI_PIPELINE_ID>'
|
||||||
annotations:
|
annotations:
|
||||||
app.gitlab.com/app: <CI_PROJECT_PATH_SLUG>
|
app.gitlab.com/app: <CI_PROJECT_PATH_SLUG>
|
||||||
app.gitlab.com/env: <CI_ENVIRONMENT_SLUG>
|
app.gitlab.com/env: <CI_ENVIRONMENT_SLUG>
|
||||||
|
@ -159,7 +159,6 @@ metadata:
|
||||||
labels:
|
labels:
|
||||||
app: <CI_PROJECT_PATH_SLUG>
|
app: <CI_PROJECT_PATH_SLUG>
|
||||||
environment: <CI_ENVIRONMENT_SLUG>
|
environment: <CI_ENVIRONMENT_SLUG>
|
||||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
|
||||||
annotations:
|
annotations:
|
||||||
prometheus.io/port: '80'
|
prometheus.io/port: '80'
|
||||||
prometheus.io/scrape: 'true'
|
prometheus.io/scrape: 'true'
|
||||||
|
@ -181,13 +180,14 @@ metadata:
|
||||||
name: engelsystem-ingress
|
name: engelsystem-ingress
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/tls-acme: 'true'
|
kubernetes.io/tls-acme: 'true'
|
||||||
kubernetes.io/ingress.class: 'nginx'
|
kubernetes.io/ingress.class: <CI_INGRESS_CLASS>
|
||||||
cert-manager.io/cluster-issuer: <CI_CLUSTER_ISSUER>
|
cert-manager.io/cluster-issuer: <CI_CLUSTER_ISSUER>
|
||||||
nginx.ingress.kubernetes.io/rewrite-target: /$1
|
nginx.ingress.kubernetes.io/rewrite-target: /$1
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: <CI_INGRESS_TRAEFIK_ENTRYPOINT>
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: 'true'
|
||||||
labels:
|
labels:
|
||||||
app: <CI_PROJECT_PATH_SLUG>
|
app: <CI_PROJECT_PATH_SLUG>
|
||||||
environment: <CI_ENVIRONMENT_SLUG>
|
environment: <CI_ENVIRONMENT_SLUG>
|
||||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
|
||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
|
@ -197,7 +197,7 @@ spec:
|
||||||
- host: <CI_INGRESS_DOMAIN>
|
- host: <CI_INGRESS_DOMAIN>
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: '<CI_INGRESS_PATH>/?(.*)'
|
- path: '<CI_INGRESS_PATH><CI_INGRESS_MATCH>'
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
nginx -g 'daemon off;'&
|
|
||||||
|
|
||||||
# If first arg starts with a `-` or is empty
|
# If first arg starts with a `-` or is empty
|
||||||
if [[ "${1#-}" != "${1}" ]] || [[ -z "${1}" ]]; then
|
if [[ "${1#-}" != "${1}" ]] || [[ -z "${1}" ]]; then
|
||||||
set -- php-fpm "$@"
|
set -- php-fpm "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Configure app url
|
||||||
|
url=$(echo "$APP_URL" | sed -n 's~https*://[^/]\+/\(.*\)~\1~p')
|
||||||
|
url=${url%/}
|
||||||
|
if [[ -n "${url}" ]]; then
|
||||||
|
echo "Url prefix: '${url}'"
|
||||||
|
sed -i "s~location /~rewrite ^/${url}(/.*)?$ /\$1;\n location /~" /etc/nginx/nginx.conf
|
||||||
|
fi
|
||||||
|
|
||||||
function get_name() {
|
function get_name() {
|
||||||
echo "$1" | cut -d: -f1
|
echo "$1" | cut -d: -f1
|
||||||
}
|
}
|
||||||
|
@ -37,4 +43,6 @@ if [[ -n "${RUN_USER}" ]]; then
|
||||||
echo "Running as $user:$group"
|
echo "Running as $user:$group"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
nginx -g 'daemon off;'&
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|
|
@ -20,9 +20,9 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
include mime.types;
|
include mime.types;
|
||||||
access_log off;
|
access_log /dev/stdout;
|
||||||
listen [::]:80 ipv6only=off;
|
listen [::]:80 ipv6only=off;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
@ -32,7 +32,7 @@ http {
|
||||||
root /var/www/public;
|
root /var/www/public;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.php?$args;
|
try_files $uri $uri/ /index.php$is_args$args;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
|
|
|
@ -2,20 +2,31 @@
|
||||||
|
|
||||||
namespace Engelsystem\Http;
|
namespace Engelsystem\Http;
|
||||||
|
|
||||||
|
use Engelsystem\Config\Config;
|
||||||
use Engelsystem\Container\ServiceProvider;
|
use Engelsystem\Container\ServiceProvider;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
||||||
|
|
||||||
class RequestServiceProvider extends ServiceProvider
|
class RequestServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
/** @var array */
|
||||||
|
protected array $appUrl;
|
||||||
|
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
|
/** @var Config $config */
|
||||||
$config = $this->app->get('config');
|
$config = $this->app->get('config');
|
||||||
$trustedProxies = $config->get('trusted_proxies', []);
|
$trustedProxies = $config->get('trusted_proxies', []);
|
||||||
|
$this->appUrl = parse_url($config->get('url') ?: '');
|
||||||
|
|
||||||
if (!is_array($trustedProxies)) {
|
if (!is_array($trustedProxies)) {
|
||||||
$trustedProxies = empty($trustedProxies) ? [] : explode(',', preg_replace('~\s+~', '', $trustedProxies));
|
$trustedProxies = empty($trustedProxies) ? [] : explode(',', preg_replace('~\s+~', '', $trustedProxies));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($this->appUrl['path'])) {
|
||||||
|
Request::setFactory([$this, 'createRequestWithoutPrefix']);
|
||||||
|
}
|
||||||
|
|
||||||
/** @var Request $request */
|
/** @var Request $request */
|
||||||
$request = $this->app->call([Request::class, 'createFromGlobals']);
|
$request = $this->app->call([Request::class, 'createFromGlobals']);
|
||||||
$this->setTrustedProxies($request, $trustedProxies);
|
$this->setTrustedProxies($request, $trustedProxies);
|
||||||
|
@ -25,6 +36,46 @@ class RequestServiceProvider extends ServiceProvider
|
||||||
$this->app->instance('request', $request);
|
$this->app->instance('request', $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $query GET parameters
|
||||||
|
* @param array $request POST parameters
|
||||||
|
* @param array $attributes Additional data
|
||||||
|
* @param array $cookies Cookies
|
||||||
|
* @param array $files Uploaded files
|
||||||
|
* @param array $server Server env
|
||||||
|
* @param mixed $content Request content
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function createRequestWithoutPrefix(
|
||||||
|
array $query = [],
|
||||||
|
array $request = [],
|
||||||
|
array $attributes = [],
|
||||||
|
array $cookies = [],
|
||||||
|
array $files = [],
|
||||||
|
array $server = [],
|
||||||
|
$content = null
|
||||||
|
): Request {
|
||||||
|
if (
|
||||||
|
!empty($this->appUrl['path'])
|
||||||
|
&& !empty($server['REQUEST_URI'])
|
||||||
|
&& Str::startsWith($server['REQUEST_URI'], $this->appUrl['path'])
|
||||||
|
) {
|
||||||
|
$requestUri = Str::substr(
|
||||||
|
$server['REQUEST_URI'],
|
||||||
|
Str::length(rtrim($this->appUrl['path'], '/'))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset paths which only contain the app path
|
||||||
|
if ($requestUri && !Str::startsWith($requestUri, '/')) {
|
||||||
|
$requestUri = $server['REQUEST_URI'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$server['REQUEST_URI'] = $requestUri ?: '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Request($query, $request, $attributes, $cookies, $files, $server, $content);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the trusted Proxies
|
* Set the trusted Proxies
|
||||||
*
|
*
|
||||||
|
|
|
@ -15,7 +15,7 @@ class RequestServiceProviderTest extends ServiceProviderTest
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function provideRegister()
|
public function provideRegister(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['', []],
|
['', []],
|
||||||
|
@ -30,15 +30,16 @@ class RequestServiceProviderTest extends ServiceProviderTest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideRegister
|
* @dataProvider provideRegister
|
||||||
* @covers \Engelsystem\Http\RequestServiceProvider::register()
|
* @covers \Engelsystem\Http\RequestServiceProvider::register
|
||||||
*
|
*
|
||||||
* @param string|array $configuredProxies
|
* @param string|array $configuredProxies
|
||||||
* @param array $trustedProxies
|
* @param array $trustedProxies
|
||||||
*/
|
*/
|
||||||
public function testRegister($configuredProxies, $trustedProxies)
|
public function testRegister($configuredProxies, array $trustedProxies)
|
||||||
{
|
{
|
||||||
/** @var Config|MockObject $config */
|
$config = new Config([
|
||||||
$config = $this->getMockBuilder(Config::class)->getMock();
|
'trusted_proxies' => $configuredProxies,
|
||||||
|
]);
|
||||||
/** @var Request|MockObject $request */
|
/** @var Request|MockObject $request */
|
||||||
$request = $this->getMockBuilder(Request::class)->getMock();
|
$request = $this->getMockBuilder(Request::class)->getMock();
|
||||||
|
|
||||||
|
@ -46,7 +47,6 @@ class RequestServiceProviderTest extends ServiceProviderTest
|
||||||
|
|
||||||
$this->setExpects($app, 'call', [[Request::class, 'createFromGlobals']], $request);
|
$this->setExpects($app, 'call', [[Request::class, 'createFromGlobals']], $request);
|
||||||
$this->setExpects($app, 'get', ['config'], $config);
|
$this->setExpects($app, 'get', ['config'], $config);
|
||||||
$this->setExpects($config, 'get', ['trusted_proxies'], $configuredProxies);
|
|
||||||
|
|
||||||
$app->expects($this->exactly(3))
|
$app->expects($this->exactly(3))
|
||||||
->method('instance')
|
->method('instance')
|
||||||
|
@ -61,9 +61,67 @@ class RequestServiceProviderTest extends ServiceProviderTest
|
||||||
->setConstructorArgs([$app])
|
->setConstructorArgs([$app])
|
||||||
->onlyMethods(['setTrustedProxies'])
|
->onlyMethods(['setTrustedProxies'])
|
||||||
->getMock();
|
->getMock();
|
||||||
$serviceProvider->expects($this->once())
|
$this->setExpects($serviceProvider, 'setTrustedProxies', [$request, $trustedProxies]);
|
||||||
->method('setTrustedProxies')
|
|
||||||
->with($request, $trustedProxies);
|
|
||||||
$serviceProvider->register();
|
$serviceProvider->register();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Http\RequestServiceProvider::register
|
||||||
|
*/
|
||||||
|
public function testRegisterRewritingPrefix()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'url' => 'https://some.app/subpath',
|
||||||
|
]);
|
||||||
|
$this->app->instance('config', $config);
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
/** @var ServiceProvider|MockObject $serviceProvider */
|
||||||
|
$serviceProvider = $this->getMockBuilder(RequestServiceProvider::class)
|
||||||
|
->setConstructorArgs([$this->app])
|
||||||
|
->onlyMethods(['createRequestWithoutPrefix'])
|
||||||
|
->getMock();
|
||||||
|
$this->setExpects($serviceProvider, 'createRequestWithoutPrefix', null, $request);
|
||||||
|
|
||||||
|
$serviceProvider->register();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide test data: [requested uri; expected rewrite, configured app url]
|
||||||
|
*
|
||||||
|
* @return string[][]
|
||||||
|
*/
|
||||||
|
public function provideRequestPathPrefix(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['/', '/'],
|
||||||
|
['/sub', '/sub'],
|
||||||
|
['/subpath2', '/subpath2'],
|
||||||
|
['/subpath2/test', '/subpath2/test'],
|
||||||
|
['/subpath', '/'],
|
||||||
|
['/subpath/', '/'],
|
||||||
|
['/subpath/test', '/test'],
|
||||||
|
['/subpath/foo/bar', '/foo/bar'],
|
||||||
|
['/path/foo/bar', '/foo/bar', 'https://some.app/path/'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Http\RequestServiceProvider::createRequestWithoutPrefix
|
||||||
|
* @dataProvider provideRequestPathPrefix
|
||||||
|
*/
|
||||||
|
public function testCreateRequestWithoutPrefix(string $requestUri, string $expected, string $url = null)
|
||||||
|
{
|
||||||
|
$_SERVER['REQUEST_URI'] = $requestUri;
|
||||||
|
$config = new Config([
|
||||||
|
'url' => $url ?: 'https://some.app/subpath',
|
||||||
|
]);
|
||||||
|
$this->app->instance('config', $config);
|
||||||
|
$serviceProvider = new RequestServiceProvider($this->app);
|
||||||
|
$serviceProvider->register();
|
||||||
|
|
||||||
|
/** @var Request $request */
|
||||||
|
$request = $this->app->get('request');
|
||||||
|
$this->assertEquals($expected, $request->getPathInfo());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue