Ş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?

mevlut usta

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

mevlut usta

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.

mevlut usta

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.