diff --git a/.github/workflows/test-app.yml b/.github/workflows/test-app.yml index 1f4cc0d..2f2367f 100644 --- a/.github/workflows/test-app.yml +++ b/.github/workflows/test-app.yml @@ -4,9 +4,9 @@ name: Test application on: push: - branches: [ "main" ] + branches: [ "non-ssl" ] pull_request: - branches: [ "main" ] + branches: [ "non-ssl" ] workflow_dispatch: permissions: diff --git a/README.md b/README.md index 241dbe8..fa7e462 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Traffic Sign Classifier [![Traffic Sign Classifier](https://img.shields.io/website-up-down-green-red/https/danieltsiang.github.io.svg)](https://traffic-sign-classifier-dt.onrender.com/) -[![Test app status](https://github.com/DanielTsiang/traffic-sign-classifier/actions/workflows/test-app.yml/badge.svg?branch=main)](https://github.com/DanielTsiang/traffic-sign-classifier/actions?query=branch%3Amain) +[![Test app status](https://github.com/DanielTsiang/traffic-sign-classifier/actions/workflows/test-app.yml/badge.svg?branch=non-ssl)](https://github.com/DanielTsiang/traffic-sign-classifier/actions?query=branch%3Anon-ssl) [![GitHub branches](https://badgen.net/github/branches/DanielTsiang/traffic-sign-classifier?&kill_cache=1)](https://github.com/DanielTsiang/traffic-sign-classifier/branches) -[![Security](https://snyk.io/test/github/DanielTsiang/traffic-sign-classifier/badge.svg?targetFile=services/flask/requirements.txt)](https://snyk.io/test/github/DanielTsiang/traffic-sign-classifier?targetFile=services/flask/requirements.txt) +[![Security](https://snyk.io/test/github/DanielTsiang/traffic-sign-classifier/non-ssl/badge.svg?targetFile=services/flask/requirements.txt)](https://snyk.io/test/github/DanielTsiang/traffic-sign-classifier/non-ssl?targetFile=services/flask/requirements.txt) [![Visitors](https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ftraffic-sign-classifier-dt.onrender.com%2F&label=Hits&countColor=%2337d67a&style=flat)](https://visitorbadge.io/status?path=https%3A%2F%2Ftraffic-sign-classifier-dt.onrender.com%2F) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Buymeacoffee](https://img.shields.io/badge/Donate-Buy%20Me%20A%20Coffee-orange.svg?style=flat&logo=buymeacoffee)](https://www.buymeacoffee.com/dantsiang8) @@ -24,7 +24,7 @@ https://user-images.githubusercontent.com/74436899/151076061-8875ae51-8c43-4dea- ## Specification * Design and build a single-page web app to serve a Machine Learning model I trained. -* Serve the Python Flask app with Gunicorn and use NGINX as the reverse proxy. Customise NGINX config to force SSL. +* Serve the Python Flask app with Gunicorn and use NGINX as the reverse proxy. * Serve the TensorFlow model using TensorFlow Serving for optimal performance and managing model versioning. * Flask, NGINX and TensorFlow Serving Docker containers are orchestrated using Docker Compose. * Users can upload their own images or select a random image for the model to classify. @@ -63,11 +63,8 @@ https://user-images.githubusercontent.com/74436899/151076061-8875ae51-8c43-4dea- ## Running locally 1. Ensure you have Docker installed. 2. To start the app, in the root folder where `docker-compose.yml` is contained, run `docker-compose up` in the terminal. -3. Visit `localhost:1337` in your web browser. Bypass the invalid SSL certificate warning. -E.g. in Chrome, click on the screen with the page open and type `thisisunsafe` to bypass the warning. -4. If you are unable to bypass the warning, checkout the `non-ssl` branch instead to run the Docker -containers without SSL enforced. In this branch, visit `localhost` instead. -5. To shut down the app, run `docker-compose down` in the terminal or hit `CTRL+C`. +3. Visit `localhost` in your web browser. +4. To shut down the app, run `docker-compose down` in the terminal or hit `CTRL+C`. ### Testing To run the integration tests, in the root folder, run the following in the terminal: diff --git a/docker-compose.yml b/docker-compose.yml index b18eec5..0843c82 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,8 @@ services: build: context: ./services/tensorflow-serving dockerfile: Dockerfile.tf + command: + - "--model_config_file=/models/model.config" container_name: tensorflow-serving ports: - "8501:8501" @@ -16,7 +18,7 @@ services: dockerfile: Dockerfile.nginx container_name: nginx ports: - - "1337:443" + - "80:80" depends_on: - flask diff --git a/services/nginx/Dockerfile.nginx b/services/nginx/Dockerfile.nginx index 05ce4c2..9d974b9 100644 --- a/services/nginx/Dockerfile.nginx +++ b/services/nginx/Dockerfile.nginx @@ -3,5 +3,3 @@ FROM nginx:1.25.2-alpine RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY cert.pem /certificate/cert.pem -COPY key.pem /certificate/key.pem diff --git a/services/nginx/cert.pem b/services/nginx/cert.pem deleted file mode 100644 index 7b4eda2..0000000 --- a/services/nginx/cert.pem +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFkDCCA3gCCQCfY05iwU8TXTANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC -R0IxEDAOBgNVBAgMB0VuZ2xhbmQxDzANBgNVBAcMBkxvbmRvbjEWMBQGA1UECgwN -RGFuaWVsIFRzaWFuZzEWMBQGA1UEAwwNRGFuaWVsIFRzaWFuZzEnMCUGCSqGSIb3 -DQEJARYYZGFuX3RzaWFuZ0Bob3RtYWlsLmNvLnVrMB4XDTIyMDExOTE4MjY1OVoX -DTIzMDExOTE4MjY1OVowgYkxCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5k -MQ8wDQYDVQQHDAZMb25kb24xFjAUBgNVBAoMDURhbmllbCBUc2lhbmcxFjAUBgNV -BAMMDURhbmllbCBUc2lhbmcxJzAlBgkqhkiG9w0BCQEWGGRhbl90c2lhbmdAaG90 -bWFpbC5jby51azCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALXbNfbo -ds5EKVLddhcAqMEwjkz77w+WpPAGezZ+TOrwR8h1NdLhaVzerVSmK56fvV0I/PSV -iCzcSxe2HfQybFnUHJDEpu4g/1kbvQftpS+Hd/l5MTyCWlrRPf68d21zDP/+gn4r -xvjy0sB7Y59jSC7vaAsHYiRIK1HtRrh2RYeLXAmzmXTmnlDI8JU1aa1uvqd8kacc -ZmlomXtbE5jwVTbmzHCJtbPlM3vlmz+9ldBp6P39AY0v2o/VnfjH1znPx0P3Yd+f -w1omy+Hqg3ZeIRyrdcm+4mZ0TPuwmPo6/l72s/z6AiidkT/CjL8yf4VDUTJeKdav -TCkMnq7nD0phYXbD72bmUz01H7VzCRJUTAloUF6krXSTuGqM4C8+CU2R2mr4kUZ+ -15TSj/xO8dKMZ0SPcg9gYUX2mEUzDUXNP6uPlqETRF2MadtreNQ3WvLTtER7x6Gj -/AaGl3yNLqWM02ghrw5SWSWeoipfreY7+qKksjPNl90RNs8GlALgjaDWbjwpVIiH -1rMVU1aIzLHksmPdv/rYCbv/xr+q1mCG7VvhIm0klW8SCQkpjnOscZ4cJMS1KtIw -dS/EgfQ/haAocL4oOEsnGuF1YdCCd0TA8o/mVOCu1sohkZ0VwfVdgNhRH0doBnWM -gg78rE80hVnZtn4GfdODu/UlBk7JfXjsI5vdAgMBAAEwDQYJKoZIhvcNAQELBQAD -ggIBAGZERMIVfU5ps7hqtzD36RcDZj8fSa9v6/ch7QHtWUvjcz1Oft8Gmu1pwQrs -UtuTXWXBQvGl0APLzjlg451JaSKBgjNIHrkJicbaxvjs9s/CeoU0M44XbNMzkBDl -dAAi0mIiJOh0LkxU/ne75NEAShEnvO8ZtWOKQzxMMST9n5JL0GRpBIPwMijypdbI -Y+rCNa+dLWNmG4gEf5k5AWOyaoLhmW0LVeLIedj2UUxCBglm1ZQTzylgkSuvxmAB -0tXP1gt7NWVL3U5hCPPKiEGxJloJ/klhxo6NGOCpJf8QEmdFk4ftkLP6msPXnvyT -/YJDMcOrMl9Z2vI3+TyW6WXd6IfQARaDljDjHEKyghTd0OHpUnIYitVL6qkRkun0 -uow8pgYIzw/oKEd21bPj+/NZG1ibwDLU44AfGbZYYiCc87jMtPHESDEQveppDjOp -YrySluSqhCBOGe6PjINQOBZcASRlAN7bdMqsMTDpvJINKcmlJCywCs+CSCjHHoI3 -QNKqZDZXR9yvVndJduR/7Qb7vr5n+i4XvVjLSB/5axV78akwAZFwjR+c2wv72RTC -nLGU6yllt6YIi3yjHYWpa+PIl1fi+XdmybUGDiCUB3ji0Z71+AOm+9iK4rB1ZjG4 -uJpNsOxsgoKEWR1oaTCVPdPNof6bUzkBmJqet5RAizDhc51J ------END CERTIFICATE----- diff --git a/services/nginx/key.pem b/services/nginx/key.pem deleted file mode 100644 index 545ae84..0000000 --- a/services/nginx/key.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -lm+eTzAfaK5E2XJOwGorIiUK/x4q5lteCQ== ------END PRIVATE KEY----- diff --git a/services/nginx/nginx.conf b/services/nginx/nginx.conf index 89ac528..e6d4b86 100644 --- a/services/nginx/nginx.conf +++ b/services/nginx/nginx.conf @@ -3,22 +3,13 @@ upstream classify { } server { - listen 443 ssl; - ssl_certificate /certificate/cert.pem; - ssl_certificate_key /certificate/key.pem; - error_page 497 https://$host:1337$request_uri; + listen 80; + server_name localhost; + location / { proxy_pass http://classify; # upstream name defined above proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host:1337; + proxy_set_header Host "localhost"; proxy_redirect off; } } - -server { - listen 80; - server_name classify; - location / { - return 301 https://$host:1337$request_uri; - } -} diff --git a/services/tensorflow-serving/Dockerfile.tf b/services/tensorflow-serving/Dockerfile.tf index aa9ec33..218f2e2 100644 --- a/services/tensorflow-serving/Dockerfile.tf +++ b/services/tensorflow-serving/Dockerfile.tf @@ -6,9 +6,3 @@ ENV PORT 8501 COPY ./model/ /models/traffic-sign-classifier/ COPY ./model.config /models/model.config - -# Fix because base tf_serving_entrypoint.sh does not take $PORT env variable while $PORT is set by Heroku -# CMD is required to run on Heroku -COPY ./tf_serving_entrypoint.sh /usr/bin/tf_serving_entrypoint.sh -RUN chmod +x /usr/bin/tf_serving_entrypoint.sh -CMD ["/usr/bin/tf_serving_entrypoint.sh"] diff --git a/services/tensorflow-serving/tf_serving_entrypoint.sh b/services/tensorflow-serving/tf_serving_entrypoint.sh deleted file mode 100644 index 6e451ff..0000000 --- a/services/tensorflow-serving/tf_serving_entrypoint.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -tensorflow_model_server --port=8500 --rest_api_port="${PORT}" --model_name="${MODEL_NAME}" --model_base_path="${MODEL_BASE_PATH}"/"${MODEL_NAME}"