East PaaS

Create a custom PaaS manager following the #East programming philosophy.


Package, to use with East Foundation, to implement custom Platform as a Service / as a Code manager to deploy easily containerized applications on container-orchestration system like Kubernetes.

This package was inspired by commercial solutions such as Platform.sh, Symfony Cloud or Heroku.

This library is able to fetch a project on a source repository (like git) in a temporary folder, reads a deployment file (by default, called .paas.yaml) run some hooks to install vendors (with composer, npm, pip, etc..), compiles (make, symfony console), warmup cache, creates OCI image (with buildah or docker build) and deploy the project them in a cluster.

The library is provider free. Thanks to interfaces defined in this library, DI and Teknoo/Recipe you can adapt this library to any orchestration system.

However, a Docker client and a Kubernetes client are bundle with the library.

You can use directly this library on your personal Docker registry and Kubernetes cluster or use any commercial solution.

A demo deployed by the project above is available here

PaaS illustration

Features


Flexible

PaaS adapts to your projects

Independent

Not require any cloud provider or on-premise solution

Docker / Kubernetes

Docker and Kubernetes clients are natively bundled

Extendable

Can be extendable only via the DI's configuration or thanks to Recipe.

GitHub


Fork the project on GitHub

It is open source! Hosted, developed, and maintained on GitHub.


View GitHub Project

Patreon


Support this project on Patreon

This project is free and will remain free, but its development is not. If you like it and help us maintain it and evolve it, don't hesitate to support us on Patreon.


Support it

File deployment example


paas: #Dedicated to compiler
    version: v1
 
#Config
maps:
    map1:
        key1: value1
        key2: ${FOO}
    map2:
        foo: bar
        bar: R{foo}
 
#Secrets provider
secrets:
    map-vault:
        provider: map #Internal secrets, must be passed in this file
        options:
            key1: value1
            key2: ${FOO}
    map-vault2:
        provider: map #Internal secrets, must be passed in this file
        options:
            hello: R{world}
    volume-vault:
        provider: map
        type: foo
        options:
            foo: bar
            bar: foo
 
#Custom image, not available in the library
images:
    foo:
        build-name: foo
        tag: latest
        path: '/images/${FOO}'
 
#Hook to build the project before container, Called in this order
builds:
    composer-build: #Name of the step
        composer:
            action: install #Hook to call
            arguments:
                - 'no-dev'
                - 'optimize-autoloader'
                - 'classmap-authoritative'
    custom-hook:
        hook-id: foo bar
 
#Volume to build to use with container
volumes:
    extra: #Name of the volume
        local-path: "/foo/bar" #optional local path where store data in the volume
        add: #folder or file, from .paas.yaml where is located to add to the volume
            - 'extra'
    other-name: #Name of the volume
        add: #folder or file, from .paas.yaml where is located to add to the volume
            - 'vendor'
 
#Pods (set of container)
pods:
    php-pods: #podset name
        replicas: 2 #instance of pods
        requires:
            - 'x86_64'
            - 'avx'
        upgrade:
            max-upgrading-pods: 2
            max-unavailable-pods: 1
        containers:
            php-run: #Container name
                image: registry.teknoo.software/php-run #Container image to use
                version: 7.4
                listen: #Port listen by the container
                    - 8080
                volumes: #Volumes to link
                    extra:
                        from: 'extra'
                        mount-path: '/opt/extra' #Path where volume will be mount
                    app:
                        mount-path: '/opt/app' #Path where data will be stored
                        add: #folder or file, from .paas.yaml where is located to add to the volume
                            - 'src'
                            - 'var'
                            - 'vendor'
                            - 'composer.json'
                            - 'composer.lock'
                            - 'composer.phar'
                        writables:
                            - 'var/*'
                    data: #Persistent volume, can not be pre-populated
                        mount-path: '/opt/data'
                        persistent: true
                        storage-size: 3Gi
                    data-replicated: #Persistent volume, can not be pre-populated
                        mount-path: '/opt/data-replicated'
                        persistent: true
                        storage-provider: 'replicated-provider'
                        storage-size: 3Gi
                    map:
                        mount-path: '/map'
                        from-map: 'map2'
                    vault:
                        mount-path: '/vault'
                        from-secret: 'volume-vault'
                variables: #To define some environment variables
                    SERVER_SCRIPT: '${SERVER_SCRIPT}'
                    from-maps:
                        KEY0: 'map1.key0'
                    import-maps:
                        - 'map2'
                    from-secrets: #To fetch some value from secret/vault
                        KEY1: 'map-vault.key1'
                        KEY2: 'map-vault.key2'
                    import-secrets:
                        - 'map-vault2'
                healthcheck:
                    initial-delay-seconds: 10
                    period-seconds: 30
                    probe:
                        command: ['ps', 'aux', 'php']
    shell:
        replicas: 1
        containers:
            sleep:
                image: registry.hub.docker.com/bash
                version: alpine
    demo:
        replicas: 1
        upgrade:
            strategy: recreate
        security:
            fs-group: 1000
        containers:
            nginx:
                image: registry.hub.docker.com/library/nginx
                version: alpine
                listen: #Port listen by the container
                    - 8080
                    - 8181
                volumes:
                    www:
                        mount-path: '/var'
                        add:
                            - 'nginx/www'
                    config:
                        mount-path: '/etc/nginx/conf.d/'
                        add:
                            - 'nginx/conf.d/default.conf'
                healthcheck:
                    initial-delay-seconds: 10
                    period-seconds: 30
                    probe:
                        http:
                            port: 8080
                            path: '/status'
                            is-secure: true
                    threshold:
                        success: 3
                        failure: 2
            waf:
                image: registry.hub.docker.com/library/waf
                version: alpine
                listen: #Port listen by the container
                    - 8181
                healthcheck:
                    initial-delay-seconds: 10
                    period-seconds: 30
                    probe:
                        tcp:
                            port: 8181
            blackfire:
                image: 'blackfire/blackfire'
                version: '2'
                listen:
                    - 8307
                variables:
                    BLACKFIRE_SERVER_ID: 'foo'
                    BLACKFIRE_SERVER_TOKEN: 'bar'
 
#Pods expositions
services:
    php-service: #Service name
        pod: "php-pods" #Pod name, use service name by default
        internal: false #If false, a load balancer is use to access it from outside
        protocol: 'TCP' #Or UDP
        ports:
            - listen: 9876 #Port listened
              target: 8080 #Pod's port targeted
    demo: #Service name
        ports:
            - listen: 8080 #Port listened
              target: 8080 #Pod's port targeted
            - listen: 8181 #Port listened
              target: 8181 #Pod's port targeted
 
#Ingresses configuration
ingresses:
    demo: #rule name
        host: demo-paas.teknoo.software
        tls:
            secret: "demo-vault" #Configure the orchestrator to fetch value from vault
        service: #default service
            name: demo
            port: 8080
        meta:
            letsencrypt: true
            annotations:
                foo2: bar
        aliases:
            - demo-paas.teknoo.software
            - alias1.demo-paas.teknoo.software
            - alias1.demo-paas.teknoo.software
            - alias2.demo-paas.teknoo.software
        paths:
            - path: /php
              service:
                  name: php-service
                  port: 9876
    demo-secure: #rule name
        host: demo-secure.teknoo.software
        https-backend: true
        tls:
            secret: "demo-vault" #Configure the orchestrator to fetch value from vault
        service: #default service
            name: demo
            port: 8181
 

Manager implementation example


//config/packages/di_bridge.yaml:
    di_bridge:
        definitions:
          - '%kernel.project_dir%/config/di.php'
    
    //config/packages/east_foundation.yaml:
    di_bridge:
        definitions:
            - '%kernel.project_dir%/vendor/teknoo/east-foundation/src/di.php'
            - '%kernel.project_dir%/vendor/teknoo/east-foundation/infrastructures/symfony/Resources/config/di.php'
            - '%kernel.project_dir%/vendor/teknoo/east-foundation/infrastructures/symfony/Resources/config/laminas_di.php'
        import:
            Psr\Log\LoggerInterface: 'logger'

    //config/packages/east_common_di.yaml:
    di_bridge:
        definitions:
            - '%kernel.project_dir%/vendor/teknoo/east-common/src/di.php'
            - '%kernel.project_dir%/vendor/teknoo/east-common/infrastructures/doctrine/di.php'
            - '%kernel.project_dir%/vendor/teknoo/east-common/infrastructures/symfony/Resources/config/di.php'
            - '%kernel.project_dir%/vendor/teknoo/east-common/infrastructures/symfony/Resources/config/laminas_di.php'
            - '%kernel.project_dir%/vendor/teknoo/east-common/infrastructures/di.php'
        import:
            Doctrine\Persistence\ObjectManager: 'doctrine_mongodb.odm.default_document_manager'
    
    //config/packages/east_paas_di.yaml:
    di_bridge:
        definitions:
            - '%kernel.project_dir%/src/di.php'
            - '%kernel.project_dir%/infrastructures/Doctrine/di.php'
            - '%kernel.project_dir%/infrastructures/Flysystem/di.php'
            - '%kernel.project_dir%/infrastructures/Git/di.php'
            - '%kernel.project_dir%/infrastructures/Kubernetes/di.php'
            - '%kernel.project_dir%/infrastructures/Image/di.php'
            - '%kernel.project_dir%/infrastructures/ProjectBuilding/di.php'
            - '%kernel.project_dir%/infrastructures/PhpSecLib/di.php'
            - '%kernel.project_dir%/infrastructures/Symfony/Components/di.php'
            - '%kernel.project_dir%/infrastructures/Laminas/di.php'
 
    //bundles.php
    ...
    Teknoo\DI\SymfonyBridge\DIBridgeBundle::class => ['all' => true],
    Teknoo\East\FoundationBundle\EastFoundationBundle::class => ['all' => true],
    Teknoo\East\CommonBundle\TeknooEastCommonBundle::class => ['all' => true],
    Teknoo\East\Paas\Infrastructures\EastPaasBundle\TeknooEastPaasBundle::class => ['all' => true],
 
    //In doctrine config
    doctrine_mongodb:
        connections:
            default:
                server: "%env(MONGODB_SERVER)%"
                options: {}
        default_database: '%env(MONGODB_NAME)%'
        document_managers:
            default:
                auto_mapping: true
                mappings:
                    TeknooEastPaas:
                        type: 'xml'
                        dir: '%kernel.project_dir%/vendor/teknoo/east-paas/infrastructures/Doctrine/config/universal'
                        is_bundle: false
                        prefix: 'Teknoo\East\Paas\Object'
                    TeknooEastPaasInfrastructuresDoctrine:
                        type: 'xml'
                        dir: '%kernel.project_dir%/vendor/teknoo/east-paas/infrastructures/Doctrine/config/odm'
                        is_bundle: false
                        prefix: 'Teknoo\East\Paas\Infrastructures\Doctrine\Object\ODM'
                    AppObjectPersisted:
                        type: 'xml'
                        dir: '%kernel.project_dir%/config/doctrine'
                        is_bundle: false
                        prefix: 'App\Object\Persisted'
 
    //In security.yaml
    security:
      //..
      providers:
        main:
          id: 'teknoo.east.website.bundle.user_provider'
 
    //In routing.yaml
    paas_admin_account:
        resource: '@TeknooEastPaasBundle/Resources/config/routing_admin_account.yaml'
        prefix: '/admin'
        schemes:    [https]
    
    paas_admin_job:
        resource: '@TeknooEastPaasBundle/Resources/config/routing_admin_job.yaml'
        prefix: '/admin'
        schemes:    [https]
 
    //in config/di.php
    return [
        //Hook
        HooksCollectionInterface::class => ...
 
        //Message
        MessageFactoryInterface::class => get(MessageFactory::class),
 
        //OCI libraries
        'teknoo.east.paas.conductor.images_library' => [...]
 
        //variables
        'teknoo.east.paas.root_dir' => ...,
 
        'teknoo.east.paas.default_storage_provider' => ...,
 
        'teknoo.east.paas.worker.tmp_dir' => ...,
        'teknoo.east.paas.worker.global_variables' => [...],
 
        'teknoo.east.paas.composer.phar.path' => ...,
 
        'teknoo.east.paas.img_builder.cmd' => ...,
        'teknoo.east.paas.img_builder.build.timeout' => ...,
        'teknoo.east.paas.img_builder.build.platforms' => ...,
 
        'teknoo.east.paas.kubernetes.ssl.verify' => ...,
    ];