I created a simple library project in microservices to study and implement FastAPI.
Docker starts 5 main services:
books
db-book
author
db-author
nginx
Everything works as expected, making requests with postman I have no problem.
Structure
Problem description
I added a test directory where I test endpoints.
Example of (incomplete) author test
from starlette.testclient import TestClient
from app.main import app
from app.api.author import authors
import logging
log = logging.getLogger('__name__')
import requests
client = TestClient(app)
def test_get_authors():
response = client.get("/")
assert response.status_code == 200
def test_get_author():
response = client.get("/1")
assert response.status_code == 200
$> docker-compose exec author_service pytest .
returns this
============================================================================================================= test session starts =============================================================================================================
platform linux -- Python 3.8.3, pytest-5.3.2, py-1.9.0, pluggy-0.13.1
rootdir: /app
collected 2 items
tests/test_author.py FF [100%]
================================================================================================================== FAILURES ===================================================================================================================
______________________________________________________________________________________________________________ test_get_authors _______________________________________________________________________________________________________________
def test_get_authors():
response = client.get("/")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404]>.status_code
tests/test_author.py:12: AssertionError
_______________________________________________________________________________________________________________ test_get_author _______________________________________________________________________________________________________________
def test_get_author():
response = client.get("/1")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404]>.status_code
tests/test_author.py:16: AssertionError
============================================================================================================== 2 failed in 0.35s ==============================================================================================================
I tried to start the tests directly from the container shell but nothing the same.
This problem occurs only with tests that are done following the documentation (using starlette / fastapi) and with requests
You can find the complete project here
Library Microsrevices example
Environment
OS:[Linux Fedora 32]
FastAPI Version [0.55.1]:
Python: [Python 3.8.3]
docker-compose file
version: '3.7'
services:
book_service:
build: ./book-service
command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
volumes:
- ./book-service/:/app/
ports:
- 8001:8000
environment:
- DATABASE_URI=postgresql://book_db_username:book_db_password#book_db/book_db_dev
- AUTHOR_SERVICE_HOST_URL=http://author_service:8000/api/v1/authors/
depends_on:
- book_db
book_db:
image: postgres:12.1-alpine
volumes:
- postgres_data_book:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=book_db_username
- POSTGRES_PASSWORD=book_db_password
- POSTGRES_DB=book_db_dev
author_service:
build: ./author-service
command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
volumes:
- ./author-service/:/app/
ports:
- 8002:8000
environment:
- DATABASE_URI=postgresql://author_db_username:author_db_password#author_db/author_db_dev
depends_on:
- author_db
author_db:
image: postgres:12.1-alpine
volumes:
- postgres_data_author:/var/lib/postgres/data
environment:
- POSTGRES_USER=author_db_username
- POSTGRES_PASSWORD=author_db_password
- POSTGRES_DB=author_db_dev
nginx:
image: nginx:latest
ports:
- "8080:8080"
volumes:
- ./nginx_config.conf:/etc/nginx/conf.d/default.conf
depends_on:
- author_service
- book_service
volumes:
postgres_data_book:
postgres_data_author:
Fixed using docker0 network ip address and with requests. Tests can now be started using 172.13.0.1 on port 8080
the main problem here are your endpoints on test file
Test example fixed:
from starlette.testclient import TestClient
from app.main import app
from app.api.author import authors
import logging
log = logging.getLogger('__name__')
import requests
client = TestClient(app)
def test_get_authors():
response = client.get("/authors") # this must be your API endpoint to test
assert response.status_code == 200
def test_get_author():
response = client.get("/authors/1") # this must be your API endpoint to test
assert response.status_code == 200
Related
I have a dockenizer flask api app that runs in localhost:5000. The api runs with no problem. But when I tried to use it by another app, which I cannot change, it uses localhost:5000/some_path.
I'd like to redirect from localhost:5000/some_path to localhost:5000.
I have read that I can use a prefix in my flask api app, but I'd prefer another approach. I don't want to mess with the code.
Is there a redirect/middleware or another way to redirect this traffic?
docker-compose.yml:
# Use root/example as user/password credentials
version: "3.1"
services:
my-db:
image: mariadb
restart: always
environment:
MARIADB_ROOT_PASSWORD: example
ports:
- 3306:3306
volumes:
- ./0_schema.sql:/docker-entrypoint-initdb.d/0_schema.sql
- ./1_data.sql:/docker-entrypoint-initdb.d/1_data.sql
adminer:
image: adminer
restart: always
environment:
ADMINER_DEFAULT_SERVER: my-db
ports:
- 8080:8080
my-api:
build: ../my-awesome-api/
ports:
- 5000:5000
If you use a web server to serve your application you could manage it with it, for example with nginx you could do:
location = /some_path {
return 301 /;
}
Or you can use a middleware:
class PrefixMiddleware(object):
def __init__(self, app, prefix=""):
self.app = app
self.prefix = prefix
def __call__(self, environ, start_response):
if environ["PATH_INFO"].startswith(self.prefix):
environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix) :]
environ["SCRIPT_NAME"] = self.prefix
return self.app(environ, start_response)
else:
#handle not found
Then register your middleware by adding the prefix to "ignore"
app = Flask(__name__)
app.wsgi_app = PrefixMiddleware(biosfera_fe.wsgi_app, prefix="/some_path")
I have the follow setup:
A simple Flask web app with a Mongo DB, each being in a docker Container, and the containers running inside a free EC2 instance.
My question is the following:
I bough a domain from Gandi, (domain.com, for example).
How can I "map" my domain to the IP address of the EC2 instance that the app is running inside of.
If I go to domain.com I want to be "redirected" to my Flask App and the url to still be domain.com, not the IP of the Instance.
Can this be achieved without Route53? Maybe with nginx?
This the docker_compose.yml file:
version: '3.5'
services:
web-app-flask:
image: web-app
ports:
- 5000:5000
mongodb_test:
image: mongo
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
mongo-express_test:
image: mongo-express
restart: always
ports:
- 8088:8081
environment:
- ME_CONFIG_MONGODB_ADMINUSERNAME=admin
- ME_CONFIG_MONGODB_ADMINPASSWORD=password
- ME_CONFIG_MONGODB_SERVER=mongodb
This the main.py of the app:
from website import create_app
app = create_app()
if __name__ == '__main__':
from waitress import serve
serve(app)
I'm following one of the various tutorials out on the internet and set up a Flask/RabbitMQ/Celery app using Docker/Docker Compose. The containers all appear to run successfully but when I hit the endpoint, the app stalls. The task appears to be stuck in PENDING and never actually completes. There are no errors in the Docker output, so I'm really confused why this isn't working. The only output I see when I hit my endpoint is this:
rabbit_1 | 2021-05-13 01:38:07.942 [info] <0.760.0> accepting AMQP connection <0.760.0> (172.19.0.4:45414 -> 172.19.0.2:5672)
rabbit_1 | 2021-05-13 01:38:07.943 [info] <0.760.0> connection <0.760.0> (172.19.0.4:45414 -> 172.19.0.2:5672): user 'rabbitmq' authenticated and granted access to vhost '/'
rabbit_1 | 2021-05-13 01:38:07.952 [info] <0.776.0> accepting AMQP connection <0.776.0> (172.19.0.4:45416 -> 172.19.0.2:5672)
rabbit_1 | 2021-05-13 01:38:07.953 [info] <0.776.0> connection <0.776.0> (172.19.0.4:45416 -> 172.19.0.2:5672): user 'rabbitmq' authenticated and granted access to vhost '/'
I'm really not sure what I am doing wrong as the documentation hasn't been much help.
Dockerfile
FROM python:3
COPY ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY . /app
ENTRYPOINT [ "python" ]
CMD ["app.py","--host=0.0.0.0"]
Flask app.py
from workerA import add_nums
from flask import (
Flask,
request,
jsonify,
)
app = Flask(__name__)
#app.route("/add")
def add():
first_num = request.args.get('f')
second_num = request.args.get('s')
result = add_nums.delay(first_num, second_num)
return jsonify({'result': result.get()}), 200
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Celery workerA.py
from celery import Celery
# Celery configuration
CELERY_BROKER_URL = 'amqp://rabbitmq:rabbitmq#rabbit:5672/'
CELERY_RESULT_BACKEND = 'rpc://'
# Initialize Celery
celery = Celery('workerA', broker=CELERY_BROKER_URL, backend=CELERY_RESULT_BACKEND)
#celery.task()
def add_nums(a, b):
return a + b
docker-compose.yml
version: "3"
services:
web:
build:
context: .
dockerfile: Dockerfile
restart: always
ports:
- "5000:5000"
depends_on:
- rabbit
volumes:
- .:/app
rabbit:
hostname: rabbit
image: rabbitmq:management
environment:
- RABBITMQ_DEFAULT_USER=rabbitmq
- RABBITMQ_DEFAULT_PASS=rabbitmq
ports:
- "5673:5672"
- "15672:15672"
worker_1:
build:
context: .
hostname: worker_1
entrypoint: celery
command: -A workerA worker --loglevel=info -Q workerA
volumes:
- .:/app
links:
- rabbit
depends_on:
- rabbit
Alright, after much research I determined that the issue was the queue name for the task. Celery was using the default name for the queue and it was causing some problems. I adjusted my decorated like so:
#celery.task(queue='workerA')
def add_nums(a, b):
return a + b
And now it works!
I made a super simple python app which I am using to learn about docker and K8s, but I am having issues with the authentication, as I get this error when the app starts:
app_1 | pymongo.errors.OperationFailure: Authentication failed., full error: {'ok': 0.0, 'errmsg': 'Authentication failed.', 'code': 18, 'codeName': 'AuthenticationFailed'}
Here is my app, I have tried with both the comment and the uncomment configuration, as I saw in this thread from SO here, but none of them works:
from flask import Flask, jsonify
from flask_pymongo import PyMongo
import os
import json_logging, logging, sys
app = Flask(__name__)
json_logging.init_flask(enable_json=True)
json_logging.init_request_instrument(app)
logger = logging.getLogger("mongodb-service")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler(sys.stdout))
# app.config['MONGO_HOST'] = os.environ['MONGODB_HOSTNAME']
# app.config['MONGO_DBNAME'] = os.environ['MONGODB_DATABASE']
# app.config['MONGO_USERNAME'] = os.environ['MONGODB_USERNAME']
# app.config['MONGO_PASSWORD'] = os.environ['MONGODB_PASSWORD']
# app.config['MONGO_AUTH_SOURCE'] = 'admin'
# mongo = PyMongo(app, config_prefix='MONGO')
app.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '#' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']
mongo = PyMongo(app)
db = mongo.db
#app.route('/')
def ping_server():
return "Welcome to the second best website ever(v2) "
#app.route('/cars')
def get_stored_cars():
_cars = db.car_tb.find()
cars = [{"brand": car["brand"], "model": car["model"]} for car in _cars]
return jsonify({"cars": cars})
......
I am connecting it to a mongodb container I create in my docker-compose:
version: "3.7"
services:
app:
container_name: cars_app
image: latalavera/flask-app:20.1
build: .
environment:
APP_ENV: "dev"
APP_DEBUG: "False"
MONGODB_DATABASE: cars_db
MONGODB_USERNAME: root
MONGODB_PASSWORD: pass
MONGODB_HOSTNAME: mongodb-flask
ports:
- ${APP_PORT}:${APP_PORT}
depends_on:
- mongodb
networks:
- app_network
mongodb:
container_name: cars_db
image: mongo:4.0
hostname: mongodb-flask
env_file:
- db-env-vars.env
volumes:
- ./init-db.js:/docker-entrypoint-initdb.d/init-db.js:ro
- ./mongo-volume:/data/db
ports:
- ${DB_PORT}:${DB_PORT}
networks:
- app_network
networks:
app_network:
name: app_net
driver: bridge
but I cannot figure out what is the issue after going through the documentation and several threads here in SO
so I found a way to make it work, I just defined the connection like this:
def get_db():
client = MongoClient(host=os.environ['MONGODB_HOSTNAME'],
port=27017,
username=os.environ['MONGODB_USERNAME'],
password=os.environ['MONGODB_PASSWORD'],
authSource="admin")
db = client[os.environ['MONGODB_DATABASE']]
return db
then defined the variables in the env filed in the dockerfile as above
I have a flask app that needs to make a request to a grpc server when a request is made to the flask endpoint.
#main.route("/someroute", methods=["POST"])
def some_function():
# Do something here
make_grpc_request(somedata)
return create_response(data=None, message="Something happened")
def make_grpc_request(somedata):
channel = grpc.insecure_channel('localhost:30001')
stub = some_proto_pb2_grpc.SomeServiceStub(channel)
request = some_proto_pb2.SomeRequest(id=1)
response = stub.SomeFunction(request)
logger.info(response)
But I keep getting an error InactiveRpcError of RPC that terminated with: StatusCode.UNAVAILABLE failed to connect to all addresses
Just putting the client code inside a normal .py file works fine, and making the request inside BloomRPC works fine too so it couldn't be a server issue.
Is this something to do with how flask works and I'm just missing something?
I have also tried using https://github.com/public/sonora without any success like this:
with sonora.client.insecure_web_channel("localhost:30001") as channel:
stub = some_proto_pb2_grpc.SomeServiceStub(channel)
request = some_proto_pb2.SomeRequest(id=1)
response = stub.SomeFunction(request)
docker-compose.yml
version: "3.7"
services:
core-profile: #This is where the grpc requests are sent to
container_name: core-profile
build:
context: ./app/profile/
target: local
volumes:
- ./app/profile/:/usr/src/app/
env_file:
- ./app/profile/database.env
- ./app/profile/jwt.env
- ./app/profile/oauth2-dev.env
environment:
- APP_PORT=50051
- PYTHONUNBUFFERED=1
- POSTGRES_HOST=core-profile-db
ports:
- 30001:50051
expose:
- 50051
depends_on:
- core-profile-db
core-profile-db:
image: postgres:10-alpine
expose:
- 5432
ports:
- 54321:5432
env_file:
- ./app/profile/database.env
app-flask-server-db:
image: postgres:10-alpine
expose:
- 5433
ports:
- 54333:5433
env_file:
- ./app/flask-server/.env
flask-server:
build:
context: ./app/flask-server/
dockerfile: Dockerfile-dev
volumes:
- ./app/flask-server:/usr/src/app/
env_file:
- ./app/flask-server/.env
environment:
- FLASK_ENV=docker
ports:
- 5000:5000
depends_on:
- app-flask-server-db
volumes:
app-flask-server-db:
name: app-flask-server-db
Your Python app (service) should reference the gRPC service as core-profile:50051.
The host name is the Compose service core-profile and, because the Python service is also within the Compose network, it must use 50051.
localhost:30001 is how you'd access it from the Compose host