Skip to content

Commit 85ef832

Browse files
authored
Merge pull request #16 from flavienbwk/f/k8s-deployment
Production and K8S deployment
2 parents eca3915 + b625661 commit 85ef832

36 files changed

+1221
-300
lines changed

.env.example

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FLASK_API_VERSION="1.1"
2+
FLASK_SERVER_NAME="My API Project"
3+
FLASK_SERVER_DESCRIPTION="Dockerized Flask API boilerplate using an LDAP and token-based authentication"
4+
FLASK_SECRET_KEY="Some secret key"
5+
6+
LDAP_ORGANISATION="My Company"
7+
LDAP_DOMAIN="mycompany.com"
8+
LDAP_HOST="ldap"
9+
LDAP_SCHEME="ldap" # "ldaps" if using secure LDAP, "ldap" else
10+
LDAP_PORT=389
11+
LDAP_USERS_DN="dc=mycompany,dc=com"
12+
LDAP_ADMIN_DN="cn=admin,dc=mycompany,dc=com"
13+
LDAP_ADMIN_PASSWORD="adminpwd"
14+
15+
POSTGRES_HOST="database"
16+
POSTGRES_PORT=5432
17+
POSTGRES_USER="myproject"
18+
POSTGRES_PASSWORD="myprojectpwd"
19+
POSTGRES_DB="myproject"

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
.vscode/
22

3+
.env
4+
35
web/app/node_modules/
4-
ldap/
56
logs/
6-
migrations/
77

88
api/database.mwb.bak

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ before_install:
1111

1212
script:
1313
- docker-compose build
14+
- docker-compose -f prod.docker-compose.yml build

README.md

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# Dockerized ReactJS, Flask, LDAP-auth boilerplate
1+
# Dockerized ReactJS, Flask, LDAP boilerplate
22

33
<p align="center">
44
<a href="https://travis-ci.org/flavienbwk/reactjs-flask-ldap-boilerplate.svg?branch=master" target="_blank">
55
<img src="https://travis-ci.org/flavienbwk/reactjs-flask-ldap-boilerplate.svg?branch=master"/>
66
</a>
77
<a href="https://codebeat.co/projects/github-com-flavienbwk-reactjs-flask-ldap-boilerplate-master"><img alt="codebeat badge" src="https://codebeat.co/badges/940a0bd0-5aa5-4f96-b6fc-39b6e1b7e14b" /></a>
88
</p>
9-
<p align="center">ReactJS + Flask + Docker<br/>boilerplate using a token-based LDAP authentication</p>
9+
<p align="center">ReactJS + Flask + Docker (+ K8S)<br/>boilerplate using a token-based LDAP authentication</p>
1010

1111
> :smiley: Suggestions and feedbacks are [highly appreciated](https://github.com/flavienbwk/reactjs-flask-ldap-boilerplate/issues/new)
1212
@@ -15,17 +15,18 @@
1515
- Docker architecture
1616
- LDAP authentication
1717
- Token-based API authentication
18-
- Automatic [token renewal](./api/app/service/auth_service.py#L44) with [a Flask middleware](./api/app/service/auth_service.py#L31)
18+
- Automatic [token renewal](./api/app/service/auth_service.py#L45) with [a Flask middleware](./api/app/service/auth_service.py#L32)
1919
- Swagger documentation
2020
- Flask-Migrate
2121
- Flask-SQLAlchemy (PostgreSQL was chosen)
22-
- [Logging and logs rotation](./api/app/utils/Logger.py#L12)
23-
- [Choose](./app/app/src/App.js#L64) between sidebar and navbar (or use both !)
22+
- [Logging and logs rotation](./api/app/utils/Logger.py#L11)
23+
- [Choose](./app/app/src/App.js#L65) between sidebar and navbar (or use both !)
2424
- Responsive design
25+
- [Production](./prod.docker-compose.yml) and [development](./docker-compose.yml) builds
2526

2627
## API documentation
2728

28-
I chose to use Swagger to document the API. Just run the API following the steps below and browse to [`http://localhost:5000`](http://localhost:5000)
29+
We've chosen Swagger to document the API. Run the API following the steps below and go to [`http://localhost:5000`](http://localhost:5000).
2930

3031
Here you can take a look at the database architecture scheme :
3132

@@ -35,76 +36,144 @@ Here you can take a look at the database architecture scheme :
3536

3637
> Reminder : there is no `password` field because we use LDAP for authentication.
3738
38-
## Setting up the API
39+
## Why using LDAP authentication ?
3940

40-
The API is made to run with an LDAP server for managing users. Whether use the provided Docker LDAP server or remove the conf. in [`docker-compose.yml`](./docker-compose.yml) and use your own LDAP server.
41+
LDAP services are used in a lot of companies and institutions around the world to manage their user accounts and rights in a central place.
4142

42-
This section will explain to you how to run this project and set-up the LDAP server with one user.
43+
With this boilerplate, you will be able to develop corporate-ready services AND avoid yourself the troubles of developing registration / password forgotten / change password / profile update code.
4344

44-
### Starting services
45+
## Getting started (development)
4546

46-
First, please change the database/LDAP passwords and keys in `docker-compose.yml`
47+
This section will explain how to properly run this project and set-up the LDAP server with one user.
4748

48-
Then, run :
49+
1. Copy the `.env.example` file to `.env`
4950

50-
```bash
51-
docker-compose up ldap phpldapadmin database adminer -d
52-
```
51+
```bash
52+
cp .env.example .env
53+
```
5354

54-
> **adminer** (PostgreSQL management) will be available through `http://localhost:8082`
55-
> **phpLDAPAdmin** (LDAP management) will be available through `https://localhost:8081`
55+
> This is a good practice so your `.env` can't be committed along with your modifications (is in `.gitignore`)
5656
57-
### Creating the first user in the LDAP
57+
2. Start the authentication services
5858
59-
Access phpLDAPAdmin with : `https://localhost:8081`
59+
```bash
60+
docker-compose up ldap phpldapadmin database adminer -d
61+
```
6062
61-
If you are not familiar with LDAP, [read my LDAP user creation guide](./CREATE_LDAP_USER.md) to add your first user
63+
- **phpLDAPAdmin** (LDAP management) will be available at `https://localhost:8081`
64+
- **adminer** (PostgreSQL management) will be available at `http://localhost:8082`
6265
63-
### Run the API
66+
**Create** your first user by accessing phpLDAPAdmin at `https://localhost:8081` and [following the LDAP user creation guide](./CREATE_LDAP_USER.md).
6467
65-
The database will be automatically set-up thanks to Flask Migrate and any future modification brought to [models](./api/app/model) will be automatically applied to the database when the API is **restarted**.
68+
3. NGINX reverse-proxy
6669
67-
```bash
68-
docker-compose up api
69-
```
70+
This boilerplate includes NGINX as a reverse proxy so we can have a unique endpoint for our app and API. Else, we would have to open two endpoints : one for the app, the other for the API.
7071
71-
Access the API and its documentation browsing [`http://localhost:5000`](http://localhost:5000)
72+
```bash
73+
docker-compose up --build -d nginx
74+
```
7275
73-
## Setting up the web application
76+
> NGINX will auto restart until you have started the app and API below.
7477
75-
:clock9: This step may take quite a lot of time due to npm's initial modules download
78+
4. Run the API
7679
77-
Just run :
80+
The database will be automatically set-up thanks to Flask Migrate and any future modification brought to [models](./api/app/model) will be automatically applied when the API is **restarted**.
7881
79-
```bash
80-
# Build is quick, **first** launch is long (expect at least 5 min.)
81-
docker-compose up app
82-
```
82+
You might wait some time before the database get updated after starting the API :
8383
84-
You can now enjoy the app on [`http://localhost:8080`](http://localhost:8080)
84+
```bash
85+
docker-compose up --build -d api
86+
```
8587
86-
> :information_source: If you want to add a dependency, just stop & re-launch a `docker-compose up app`. You won't have to wait as for first launch.
88+
> For development, go to [`http://localhost:5000`](http://localhost:5000) to access the API documentation
8789
88-
## Why using LDAP authentication ?
90+
5. Run the web app
8991
90-
LDAP services are used in a lot of companies and institutions around the world to manage their user accounts and rights in a central place.
92+
```bash
93+
# Expect several minutes for first launch (npm install)
94+
docker-compose up --build -d app
95+
```
9196
92-
With this boilerplate, you will be able to develop corporate-ready services AND avoid yourself the troubles of developing registration / password forgotten / change password / profile update code.
97+
> :information_source: If you want to add a NPM package, just stop & re-launch `docker-compose up app`.
98+
99+
Enjoy the app on [`http://localhost:8080`](http://localhost:8080)
100+
101+
## Deploy to production
102+
103+
1. Prepare environment configuration
104+
105+
Copy `.env.example` to `.env` and **edit** the passwords, keys and credentials
106+
107+
Use the provided Docker LDAP server or edit the config to use your own.
108+
109+
```bash
110+
cp .env.example .env
111+
```
112+
113+
2. Build the application for production
114+
115+
```bash
116+
docker-compose -f prod.docker-compose.yml build
117+
```
118+
119+
3. Start the authentication services
120+
121+
```bash
122+
docker-compose up ldap phpldapadmin database adminer -d
123+
```
124+
125+
**Create** your first user by accessing phpLDAPAdmin at `https://localhost:8081` and [following the LDAP user creation guide](./CREATE_LDAP_USER.md).
126+
127+
:information_source: We recommend you to hide this service behind a firewall
128+
129+
4. Run the application
130+
131+
```bash
132+
docker-compose -f prod.docker-compose.yml up nginx api app -d
133+
```
134+
135+
Access the UI at `https://localhost:8080`
136+
137+
### Deploy to K8S
138+
139+
I pretend you have here your K8S instance configured to be accessed by your `kubectl` CLI.
140+
141+
I've used [Scaleway Kapsule](https://www.scaleway.com/en/kubernetes-kapsule) to perform my tests. This is an easy way to have a Kubernetes cluster quickly ready.
142+
143+
1. Building production images (optional)
144+
145+
Images are tagged `flavienb/reactjs-flask-ldap-boilerplate-{api,web,nginx}:latest` by default. Edit it in `prod.docker-compose.yml` before building.
146+
147+
:information_source: You might be interested in pushing your images in a private registry (e.g: [Scaleway's Container Registry service](https://www.scaleway.com/en/container-registry/)).
148+
149+
```bash
150+
docker-compose -f prod.docker-compose.yml build
151+
```
152+
153+
Finally, `docker push` the 3 images and edit K8S' configurations :
154+
155+
- [k8s/app.yaml, line 31](k8s/app.yaml#L31)
156+
- [k8s/api.yaml, line 21](k8s/api.yaml#L21)
157+
- [k8s/nginx.yaml, line 21](k8s/nginx.yaml#L21)
158+
159+
2. Add a new `reactjs-flask-ldap-boilerplate` namespace
93160

94-
## Left TODOs
161+
```bash
162+
kubectl create namespace my-app
163+
```
95164

96-
API :
165+
3. Configure your Ingress app endpoint
97166

98-
_Completed_
167+
- **Edit** the env variables in [k8s/env-configmap.yaml](./k8s/env-configmap.yaml)
168+
- **Edit** the app and phpldapadmin endpoints in [k8s/ingress.yaml, line 10 and 35](./k8s/ingress.yaml#L10)
169+
- **Check** the PersistentVolumeClaim if you're not using Scaleway in all `*-pvc.yaml` files
99170
100-
App :
171+
Deploy with :
101172
102-
_Completed_
173+
```bash
174+
kubectl apply -f ./k8s
175+
```
103176
104-
Architecture :
177+
4. Configure the first user
105178
106-
- [P3] Add "Deploy to prod" guide in README
107-
- [P3] Create a `prod.docker-compose.yml` file that :
108-
- Uses NGINX with SSL
109-
- Builds & serves the front-end
110-
- Disables Swagger UI
179+
**Create** your first user by accessing phpLDAPAdmin at [endpoint defined](./k8s/ingress.yaml#L35) and [following the LDAP user creation guide](./CREATE_LDAP_USER.md).

api/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM python:3.7-alpine
22

33
# python-ldap requirements
4-
RUN apk add openldap-dev libc-dev gcc
4+
RUN apk update && apk add openldap-dev libc-dev gcc g++
55

66
# psycopg2 requirements
77
RUN apk add libpq python3-dev musl-dev postgresql-dev

api/app/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ def create_app():
2121
app = Flask(__name__)
2222
app.register_error_handler(404, page_not_found)
2323
app.config.from_object(config_by_name[FLASK_LEVEL])
24+
os.environ["FLASK_ENV"] = config_by_name[FLASK_LEVEL].FLASK_ENV
2425
database.initDatabase(app)
2526
return app

api/app/config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,21 @@ class Config:
2020

2121
class DevelopmentConfig(Config):
2222
DEBUG = True
23-
SQLALCHEMY_TRACK_MODIFICATIONS = False
23+
SQLALCHEMY_TRACK_MODIFICATIONS = True
24+
FLASK_ENV = "development"
2425

2526

2627
class TestingConfig(Config):
2728
DEBUG = True
2829
TESTING = True
2930
PRESERVE_CONTEXT_ON_EXCEPTION = False
3031
SQLALCHEMY_TRACK_MODIFICATIONS = False
32+
FLASK_ENV = "development"
3133

3234

3335
class ProductionConfig(Config):
3436
DEBUG = False
37+
FLASK_ENV = "production"
3538

3639

3740
config_by_name = dict(

api/app/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
app.app_context().push()
1717

1818
manager = Manager(app)
19-
migrate = Migrate(app, database.getDatabase())
19+
migrate = Migrate(app, database.getDatabase(), "/migrations")
2020
manager.add_command('db', MigrateCommand)
2121

2222
# Commands

api/app/service/auth_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,12 @@ def authLDAPUser(username: str, password: str):
6767
# from users's LDAP details
6868
sql_datetime = datetime.datetime.utcnow()
6969
last_id = (UserService.getLastUserID() + 1)
70+
first_name = user_details[0][1]["givenName"][0].decode('utf-8')\
71+
if "givenName" in user_details[0][1] else "" # givenName is optional in LDAP
7072
UserService.createUser({
7173
"ids": sha256(hash_id(last_id) + str(time.time())),
7274
"username": username,
73-
"first_name": user_details[0][1]["givenName"][0].decode('utf-8'),
75+
"first_name": first_name,
7476
"last_name": user_details[0][1]["sn"][0].decode('utf-8'),
7577
"created_at": sql_datetime,
7678
"updated_at": sql_datetime

api/app/service/user_service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ def updateLDAPUser(user: User):
6868
ldap_user = connection.search_s(LDAP_USERS_DN, ldap.SCOPE_SUBTREE, search_filter)
6969
if len(ldap_user):
7070
ldap_user_details = {
71-
"first_name": ldap_user[0][1]["givenName"][0].decode('utf-8'),
71+
"first_name": ldap_user[0][1]["givenName"][0].decode('utf-8')\
72+
if "givenName" in ldap_user[0][1] else "", # givenName is optional in LDAP
7273
"last_name": ldap_user[0][1]["sn"][0].decode('utf-8')
7374
}
7475
user_details = {

api/entrypoint.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/bin/sh
2+
# These commands are here because they must be run at runtime
23

34
python /app/manager.py db init
45

api/prod.Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM python:3.7-alpine
2+
3+
# python-ldap requirements
4+
RUN apk update && apk add openldap-dev libc-dev gcc g++
5+
6+
# psycopg2 requirements
7+
RUN apk add libpq python3-dev musl-dev postgresql-dev
8+
9+
COPY ./requirements.txt .
10+
RUN pip install -r requirements.txt
11+
12+
# Install WSGI server
13+
RUN pip install gunicorn==20.1.0
14+
15+
WORKDIR /app
16+
COPY ./app /app
17+
18+
# Run migrations and WSGI server
19+
COPY ./prod.entrypoint.sh /entrypoint.sh
20+
ENTRYPOINT [ "/entrypoint.sh" ]

api/prod.entrypoint.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
# These commands are here because they must be run at runtime
3+
4+
python /app/manager.py db init
5+
6+
python /app/manager.py db migrate --message 'initial database migration'
7+
python /app/manager.py db upgrade
8+
9+
gunicorn manager:app -b 0.0.0.0:5000

0 commit comments

Comments
 (0)