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
|
||||
- export CI_ENVIRONMENT_URL="${CI_ENVIRONMENT_URL:-https://${CI_PROJECT_PATH_SLUG}.${KUBE_INGRESS_BASE_DOMAIN}/}"
|
||||
- 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_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
|
||||
# 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}"}
|
||||
|
@ -328,6 +332,7 @@ deploy:
|
|||
done
|
||||
|
||||
- echo "Deploying to ${CI_ENVIRONMENT_URL}"
|
||||
- kubectl diff -f deployment.yaml || true
|
||||
- kubectl apply -f deployment.yaml
|
||||
- >-
|
||||
kubectl -n $CI_KUBE_NAMESPACE wait --for=condition=Ready pods --timeout=${CI_WAIT_TIMEOUT:-5}m
|
||||
|
|
|
@ -66,7 +66,6 @@ metadata:
|
|||
labels:
|
||||
app: <CI_PROJECT_PATH_SLUG>
|
||||
environment: <CI_ENVIRONMENT_SLUG>
|
||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
|
@ -105,6 +104,7 @@ spec:
|
|||
environment: <CI_ENVIRONMENT_SLUG>
|
||||
tier: application
|
||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
||||
pipeline: '<CI_PIPELINE_ID>'
|
||||
annotations:
|
||||
app.gitlab.com/app: <CI_PROJECT_PATH_SLUG>
|
||||
app.gitlab.com/env: <CI_ENVIRONMENT_SLUG>
|
||||
|
@ -159,7 +159,6 @@ metadata:
|
|||
labels:
|
||||
app: <CI_PROJECT_PATH_SLUG>
|
||||
environment: <CI_ENVIRONMENT_SLUG>
|
||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
||||
annotations:
|
||||
prometheus.io/port: '80'
|
||||
prometheus.io/scrape: 'true'
|
||||
|
@ -181,13 +180,14 @@ metadata:
|
|||
name: engelsystem-ingress
|
||||
annotations:
|
||||
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>
|
||||
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:
|
||||
app: <CI_PROJECT_PATH_SLUG>
|
||||
environment: <CI_ENVIRONMENT_SLUG>
|
||||
commit: '<CI_COMMIT_SHORT_SHA>'
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
|
@ -197,7 +197,7 @@ spec:
|
|||
- host: <CI_INGRESS_DOMAIN>
|
||||
http:
|
||||
paths:
|
||||
- path: '<CI_INGRESS_PATH>/?(.*)'
|
||||
- path: '<CI_INGRESS_PATH><CI_INGRESS_MATCH>'
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
nginx -g 'daemon off;'&
|
||||
|
||||
# If first arg starts with a `-` or is empty
|
||||
if [[ "${1#-}" != "${1}" ]] || [[ -z "${1}" ]]; then
|
||||
set -- php-fpm "$@"
|
||||
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() {
|
||||
echo "$1" | cut -d: -f1
|
||||
}
|
||||
|
@ -37,4 +43,6 @@ if [[ -n "${RUN_USER}" ]]; then
|
|||
echo "Running as $user:$group"
|
||||
fi
|
||||
|
||||
|
||||
nginx -g 'daemon off;'&
|
||||
exec "$@"
|
||||
|
|
|
@ -20,9 +20,9 @@ http {
|
|||
}
|
||||
|
||||
server {
|
||||
include mime.types;
|
||||
access_log off;
|
||||
listen [::]:80 ipv6only=off;
|
||||
include mime.types;
|
||||
access_log /dev/stdout;
|
||||
listen [::]:80 ipv6only=off;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
@ -32,7 +32,7 @@ http {
|
|||
root /var/www/public;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$args;
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
|
|
|
@ -2,20 +2,31 @@
|
|||
|
||||
namespace Engelsystem\Http;
|
||||
|
||||
use Engelsystem\Config\Config;
|
||||
use Engelsystem\Container\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
||||
|
||||
class RequestServiceProvider extends ServiceProvider
|
||||
{
|
||||
/** @var array */
|
||||
protected array $appUrl;
|
||||
|
||||
public function register()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $this->app->get('config');
|
||||
$trustedProxies = $config->get('trusted_proxies', []);
|
||||
$this->appUrl = parse_url($config->get('url') ?: '');
|
||||
|
||||
if (!is_array($trustedProxies)) {
|
||||
$trustedProxies = empty($trustedProxies) ? [] : explode(',', preg_replace('~\s+~', '', $trustedProxies));
|
||||
}
|
||||
|
||||
if (!empty($this->appUrl['path'])) {
|
||||
Request::setFactory([$this, 'createRequestWithoutPrefix']);
|
||||
}
|
||||
|
||||
/** @var Request $request */
|
||||
$request = $this->app->call([Request::class, 'createFromGlobals']);
|
||||
$this->setTrustedProxies($request, $trustedProxies);
|
||||
|
@ -25,6 +36,46 @@ class RequestServiceProvider extends ServiceProvider
|
|||
$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
|
||||
*
|
||||
|
|
|
@ -15,7 +15,7 @@ class RequestServiceProviderTest extends ServiceProviderTest
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function provideRegister()
|
||||
public function provideRegister(): array
|
||||
{
|
||||
return [
|
||||
['', []],
|
||||
|
@ -30,15 +30,16 @@ class RequestServiceProviderTest extends ServiceProviderTest
|
|||
|
||||
/**
|
||||
* @dataProvider provideRegister
|
||||
* @covers \Engelsystem\Http\RequestServiceProvider::register()
|
||||
* @covers \Engelsystem\Http\RequestServiceProvider::register
|
||||
*
|
||||
* @param string|array $configuredProxies
|
||||
* @param array $trustedProxies
|
||||
*/
|
||||
public function testRegister($configuredProxies, $trustedProxies)
|
||||
public function testRegister($configuredProxies, array $trustedProxies)
|
||||
{
|
||||
/** @var Config|MockObject $config */
|
||||
$config = $this->getMockBuilder(Config::class)->getMock();
|
||||
$config = new Config([
|
||||
'trusted_proxies' => $configuredProxies,
|
||||
]);
|
||||
/** @var Request|MockObject $request */
|
||||
$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, 'get', ['config'], $config);
|
||||
$this->setExpects($config, 'get', ['trusted_proxies'], $configuredProxies);
|
||||
|
||||
$app->expects($this->exactly(3))
|
||||
->method('instance')
|
||||
|
@ -61,9 +61,67 @@ class RequestServiceProviderTest extends ServiceProviderTest
|
|||
->setConstructorArgs([$app])
|
||||
->onlyMethods(['setTrustedProxies'])
|
||||
->getMock();
|
||||
$serviceProvider->expects($this->once())
|
||||
->method('setTrustedProxies')
|
||||
->with($request, $trustedProxies);
|
||||
$this->setExpects($serviceProvider, 'setTrustedProxies', [$request, $trustedProxies]);
|
||||
$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