- Şu tarihte
ExpressJS + MongoDB Konteynerizasyonu
Birkaç yıldır konteynerizasyon veya dockerization/containerization yoğun bir şekilde gündemde, gelelim neler yapabilirize.
Projeyi yaptınız tıkır tıkır çalışıyor bilgisayarınızda. Geldi canlıya alma zamanı. Açarsınız DO, Linode, AWS, GCP, Azure artık allah ne verdiyse bir sunucu oluşturup SSH'dan terminalinizin başına oturursunuz başlarsınız NodeJS, MongoDB kurmaya.. Her adımda en az 1-2 hata alıp bunu nasıl çözeriz diye Google'da 2. sayfalara kadar dolanırsınız. Bazen kurmaya çalıştığınız altyapıyı oluşturanlarda bir uyumsuzluk bazen de yazdığınız projenin kullandığı bağımlılıkların uyumsuzluğu ile başınız ağrır. Zamanla bunları çözersiniz hatta kurulum betikleri yazarsınız. Yeni sürümleri, dağıtım yükseltmeleri, kernel güncellemelerini unutursunuz. npm run audit
çözmez bu noktada sorunları çünkü daha arkaplanda güvenlik açıkları oluşmuş fakat siz farkına varamamışsınızdır. Hepsini yaptınız aldınız canlıya, süpriz sorunlar burada başlar. Mongo'yu doğru ayarlayamamışsınızdır, Express ile olan bağlantıyı tam yapamamışsınızdır vd. Hadi bunları da çözdünüz, sunucu değiştirmeniz veya dağıtmanız gerekti. Tekrardan bu işlemleri yapmanız gerekecek. Bunların önüne geçmek için projeyi konteyner haline getirip Docker aracılığı ile projemizi dağıtılabilir ve sizde nasıl çalışıyorsa herkeste aynı şekilde çalışacak şekilde kullanabilirsiniz.
Konteyner?
Evvela konteyner nedir? Docker nedir? sorularına yanıt verelim: Konteyner, projenizin çalıştığı işletim sistemi ve gerekli tüm bağımlılıkların bir imaj haline getirilip çalıştırıldığı parçasıdır. Burada kullanacağınız sistem ve servislerin (node, mongo, mysql, redis vd.) imajlarını edinerek projenizi bunların içinde çalıştırma imkanı sağlıyorsunuz. Avantajları, taşınabilirlik, dağıtılabilirlik, yüksek ölçeklenebilirlik, teslim süreçlerinin pratikleştirilmesi, yönetimi ve süreç izolasyonu.
Docker ise bu yaklaşımı ticari olarak bir ürüne dönüştürüp bizlere sunan üründür. Konteyner yaklaşımı Linux işletim sistemi altında işletim sistemi düzeyinde sanallaştırma çözümü olarak LXC adıyla 2008'den beridir geliştirilmeye devam edilmektedir. Docker ise 2013 yılından beridir piyasadadır. Docker'ın kullanımı kolay ve hemen hemen her türlü işinizi görecek imajları bulunduran bir registry'e sahiptir. Docker'a üye olmadan da docker kullanıp imajları kullanabilirsiniz fakat kendi imajınızı registry'de bulundurmak için üye olmanız gerekmektedir.
Bizim de projemizi her makinede aynı şekilde çalışacak biçimde dağıtmak, kurulum sırasında oluşabilecek hatalar ve sorunlar ile uğraşmamak için Docker kullanmamız elzem oldu. ExpressJS ile - genelde - "backend" projeleri yapılmakta, ben de öncelikleri değerlendirerek zamanında Express'i seçmiştim. Sunucumuzu genelde bir HTTP sunucu yazılımı ile 80 veyahut siz hangi portta yayın yapmasını istiyorsanız o portta olacak şekilde ayarlarsınız. Apache, NGINX, Litespeed (Açık kaynak alternatifi olarak OpenLiteSpeed & WordPress ile Performans, Tomcat, Jetty, IIS en çok bilinen HTTP sunucu yazılımlarıdır. Benim senaryomda Apache vd. yazılımlara ihtiyaç duymadığım için yığına eklemedim fakat sizin ihtiyacınız varsa ekleyebilirsiniz.
Projede ExpressJS, NodeJS, MongoDB ve son olarak RabbitMQ kullanıldığı için bu yazıda da bunların nasıl birlikte konteynerize edeceğimizi anlatacağım. Docker-compose isimli Docker ile yapacağınız işlemleri kolaylaştıran ara bir yazılım ile ağ, konteyner, image işlemlerimizi bir konfigürasyon dosyası ile (docker-compose.yaml
) tüm süreci tek bir komut ile işletebilmemizi sağlayacağız.
Mevlüt Usta'nın Tarifi
Ne gerekli söyleyeyim:
- ExpressJS uygulamamız NodeJS üzerinde çalıştığı için öncelikle NodeJS image'ine ihtiyacımız var.
- API'ımız da yapılacak işlemlerin kaydedilmesi için veritabanına ihtiyacımız var, bu projede MongoDB'yi tercih ettik o yüzden onun image'ine ihtiyacımız var.
- Belirli işlemlerin arkaplanda birbiri ile iletişimde kalması için RabbitMQ adlı AMQP/MQTT yazılımını kullanıyoruz bu yüzden onun da image'ine ihtiyacımız var.
Tenceremizi hazırlayalım
Öncelikle Mongo ve RabbitMQ bizim için kullanılacak yazılımlar fakat NodeJS ile bir yazılım ayağa kaldıracağımız için mantıken yeni bir image yaratmamız gerekiyor. O yüzden Dockerfile
ile yazdığımız yazılımın image'ini oluşturacağız, nasıl mı?
FROM node:15.11.0-alpine3.10 # Kullanacağımız NodeJS versiyonu 15.11.0 ve Alpine Linux işletim sisteminin 3.10 sürümü
RUN mkdir -p /usr/src/app # RUN komutu ile Dockerdan gelmekte ve `mkdir` ile ilgili adreste yeni bir klasör oluşturuyoruz.
WORKDIR /usr/src/app # Bu komut ile Dockera çalışacağımız klasörün adresini belirtiyoruz
COPY package.json ./ # Projemizdeki package.json dosyasını çalışma klasörümüze kopyalıyoruz.
RUN npm install # NPM ile proje bağımlılıklarımızı kuruyoruz.
COPY . /usr/src/app/ # Oluşan node_modules klasörünü ve içindekileri çalışma klasörümüze kopyalıyoruz.
EXPOSE 3000 # Yayın yapılacak portumuzu seçiyoruz.
CMD npm start # NPM ile projemizi ayağa kaldırıyoruz.
Böylelikle projemizin image'ini oluşturmuş olduk. Şimdi gelelim bunları docker-compose ile harmanlamakta, bunun içinde docker-compose.yaml
dosyası oluşturmamız gerekiyor.
version: '3'
services:
api:
container_name: api
restart: unless-stopped
build:
context: .
dockerfile: Dockerfile
ports:
- '3000:3000'
links:
- mongo
command: npm start
networks:
api-network:
ipv4_address: 172.18.0.2
mongo:
container_name: mongo
restart: unless-stopped
networks:
api-network:
ipv4_address: 172.18.0.3
image: mongo
command: mongod --port 27017
volumes:
- ./data:/data/db
ports:
- '27017:27017'
rabbit:
container_name: rabbit
restart: unless-stopped
networks:
api-network:
ipv4_address: 172.18.0.4
image: rabbitmq:3-management-alpine
volumes:
- ./rabbitmq/data:/var/lib/rabbitmq/mnesia/rabbit@my-rabbit
- ./rabbitmq/logs:/var/log/rabbitmq/log
ports:
- 5672:5672
- 15672:15672
networks:
api-network:
driver: bridge
ipam:
config:
- subnet: 172.18.0.0/24
volumes:
data:
"service"lerimiz konteynerlerimiz olacak, onları da bu dosyada belirtilen ayarlara göre ayağa kaldıracağız. Burada ayrıca bir bridge network kurulmuş, ayağa kaldırdığınızda hangi uygulama hangi IP'de çalışıyor burada belirtiyoruz. Bu konfigürasyonları kendinize göre değiştirip geliştirebilirsiniz. Mongo için son oluşturulan image (mongo:latest), RabbitMQ için "management" panelini kullanabilmemiz için rabbitmq:3-management-alpine imagelerini kullandık. 172.18.0.0/24
subnet'ini kendimize ayırdık ve konteynerlerimizin portlarını ayarladık. Makinenizde docker ve docker-compose'un kurulu olduğunu varsayarsak bu Dockerfile
ve docker-compose.yaml
dosyasının da aynı klasörde bulunması gerektiğini söylememiz gerekir. Her şey hazırsa, docker-compose up -d
komutunu uygulayarak konteynerleri sessizce arkada çalışacak şekilde ayağa kaldırabilirsiniz. Mevcut konteynerleri görmek için docker ps
, bu organizasyonu tatile çıkarmak için docker-compose down
komutlarını uygulayabilirsiniz. Bunlar cihazdaki image'leri silmeyecektir. Var olan imageleri görmek için docker images
komutunu uygularsanız oluşturulan ve indirilen imageleri görebilirsiniz. Bir image'i silmek için docker image rm -f IMAGE ADI veya ID'si
ile şayet herhangi bir konteynere bağlı olmayan imageleri silmek etrafı bir toplamak isterseniz docker system prune -af
yazarsanız gereksiz network, image vd. şeyleri sistemden kaldıracaktır.
Yukarıdaki docker-compose işlemlerini gerçekleştirdiğinizde projeniz 172.18.0.2:3000
adresinden ulaşılabilir olacaktır. Uygulamanızın Mongo ve RabbitMQ ile olan iletişimi için de projenizde gerekli değişiklikleri yapmanız gerekmektedir. Elinizdeki Dockerfile ile oluşturulmuş image'inizi Docker Hub'a push edebilir, public/private şekilde ayarlayabilir dilediğinizde docker pull
diyerek istediğiniz image'i çekebilirsiniz, çektiğiniz image'leri de docker run
ile çalıştırabilir docker-compose olmadan da bu süreci kurgulayabilirsiniz fakat docker-compose ile kesinlikle daha kolay ve sürdürülebilir olduğu aşikar. Güvenlik konusunda da şayet makinenizde izinsiz bir şekilde "root" olunamıyorsa büyük bir tehlike söz konusu diyemeyiz. Çünkü docker'a ulaşmak için root yetkilerine sahip olmanız gerekmektedir. Bu yöntemde oluşturulan konteynerde kaynak kodlarınız bulunmaktadır, kaynak kodunuzu gizleyecek veya şaşırtacak (obfuscation) bir yöntem ile kaynak kodunuzu da koruyabilirsiniz.