Docker EXPOSE

Bir etkinlik sırasında EXPOSE kullanımıyla ilgili bir anlaşmazlığa düştük, ben de biraz inceleyip onunla ilgili bir yazı yazmak istedim. Öncelikle Dockerfile referans dökümanından EXPOSE ile ilgili verilen bilgiye bakalım.

The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified. The EXPOSE instruction does not actually publish the port. It functions as a type of documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published. To actually publish the port when running the container, use the -p flag on docker run to publish and map one or more ports, or the -P flag to publish all exposed ports and map them to high-order ports.

Dökümana göre Dockerfile içinde yazılan EXPOSE ifadesi herhangi bir port yönlendirme(publish) yapmıyor, çalıştırma sırasında uygulamanın hangi portların dışarı açabileceği konusunda Dockerı bilgilendiriyor.

Daha iyi anlamak için hemen bir örnek yapalım. Aşağıdaki Dockerfile ı kullanarak oluşturduğum bir imajım var.

FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "8123"]

Görüldüğü üzere bu Dockerfile içinde herhangi bir port tanımlaması yapmadık. Sadece uvicorn uygulamasına 8123 portundan çalışacağını söyledik.

Bunu aşağıdaki komutla çalıştırıp istek attığımızda beklediğimiz gibi çalıştığını görebiliyoruz.

docker run --rm --name test-without-expose -p 8123:8123 askinozgur/publish-port-without-expose
curl http://localhost:8123
{"Hello":"World"}

Dökumanın söylediği, bizim de test edip gördüğümüz gibi port yönlendirebilmek için EXPOSE kullanmamız şart değil.

Peki EXPOSE bilgilendirme dışında başka hiç bir işe yaramıyor mu? Aslında dockerı çalıştırırken -P parametresini verirsek EXPOSE ile tanımlanmış bütün portları boşta bulunan bir porta yönlendirmiş olacağız.

Aşağıdaki Dockerfile kullanılarak oluşturulmuş imajı çalıştırdığımız bir örneğe bakalım.

FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app

EXPOSE 8123

CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "8123"]
docker run --rm --name test-with-expose -P askinozgur/publish-port-with-expose
docker ps
CONTAINER ID   IMAGE                                 COMMAND                  CREATED         STATUS         PORTS                                         NAMES
b3be17aef125   askinozgur/publish-port-with-expose   "uvicorn app.main:ap…"   5 seconds ago   Up 5 seconds   0.0.0.0:49157->8123/tcp, :::49157->8123/tcp   test-with-expose

docker ps çıktısınd görüldüğü gibi içerdeki 8123 portu dışarıya 49157 olarak yönlendirildi. Bu port her yeni konteyner oluşturulduğunda değişecektir.

Bu yazıda kullandığım örneklere aşağıdaki github reposundan erişebilirsiniz.

https://github.com/askin/docker-examples


Docker Iptables Kurallarını Sallamıyor

Farketmem nedense biraz zaman aldı, bir docker container’ının portunu publish ettiğimizde arka tarafta bizden habersiz o port erişime açılıyor. UFW, FirewallD ya da doğrudan iptables kullanmanız farketmiyor.

Bazı servislere gelen anlamsız istekleri incelerken farkettim bunu. Çözmem de biraz sancılı oldu, yanlış bir firewall ayarı nedeniyle böyle birşey olduğunu düşündüğüm için çözüm bulmam zorlaştı. Ama sorun docker’ın bir port publish edildiğinde onun için otomatik olarak iptables kuralları eklemesiymiş. Burada ilgili docker dökümanı bulunmakta. Siz de böyle bir durumdaysanız aşağıdaki yöntemlerden birini kullanabilirsiniz.

/etc/docker/daemon.json dosyasına aşağıdaki satırları ekledikten sonra docker daemonu yeniden başlatabilirsiniz.

{
  "iptables" : false
}

Ya da

Docker servisini başlatırken --iptables=false parametresiyle başlatabilirsiniz.

Bu yazıda aynı problemi yaşayan başka birisi var. Eğlenceli bir yazı olmuş ona da bakabilirsiniz. Why Docker and Firewall don’t get along with each other!


İngilizce Klavye Düzeninde Türkçe Karakter Kullanmak

Yazılım geliştirirken ingilizce klavye kullanmak çok pratik oluyor. Geliştirme sırasında bolca kullandığım [] ` {} ~ \ | gibi karakterler ingilizce klavyede erişimin çok kolay olduğu bölgelerde, bunun yanında türkçe karakterlere hemen hemen hiç ihtiyacım olmuyor. Bu nedenle bir süredir ingilizce klavye kullanmaya başladım. Bunun yanında günlük iletişimin çoğunluğu türkçe. İş arkadaşlarım ve yakın çevremle konuşurken türkçe karakter kullanmamak pek sorun olmuyor, fakat bir müşteriye mail atarken, ya da şuan olduğu gibi blog yazarken türkçe karakter kullanmak şart oluyor.


Linuxda du kullanırken gizli dosyaları dahil etmek

du komutunu çok sık kullanıyorum. Makinede yer sıkıntısı yaşadığım zaman kesinlikle hayat kurtarıyor. Genellikle aşağıdaki şekilde kullanıyorum.

du -sh * | sort -h

Bu komut anlaşılır bir birimle tüm alt dizinlerin boyutlarını hesaplayıp küçükten büyüğe doğru sıralıyor. Bu komutun ve birçok linux komutunun sıkıntısı, wildcard kullanıldığında gizli dosyaları/dizinleri göstermiyor. Tabi bu kullandığınız kabukla ilgili. Ben bash kullanıyorum. Bunu aşmak için komutu aşağıdaki şekilde kullanabilirsiniz.

du -sh .[!.]* * | sort -h

Bu komutun da biraz sıkıntılar var. Mesela dosya ismi iki nokta ile başlıyorsa listelemeyecek malesef. Fakat bu şekilde işimi görüyor.


Screen ile Irssi Kullanım Kılavuzu

irssi komut satırından çalışan ve bir çok geliştirici için popüler olan bir IRC istemcisidir. Irssi GNU Screen programı ile birlikte kullanılarak çıkış yapmadan bir veya birden fazla kabuk yaratabilirsiniz. Sürekli biligisayar değiltirip fakat irssi’a kaldığınız yerden devam etmek istediğinizde çok kullanılışılır.


Usb bellek üzerinde Raspberry PI

Malesef sd kart olmadan raspberry pi’yı boot etmemiz mümkün olmuyor. Benim elimde 1GB’lık bir sd kart vardı ve 2GB dan daha ufak imaj dosyaları bulamadım. Bu nedenle usb bellek ile boot etme ihtiyacı duydum.
Benim sistemimde usb bellek ve sd kart aşağıdaki şekilde tanındı:

  • usb: /dev/sdd
  • sd: /dev/sde

Öncelikle indirdiğimiz imaj dosyasını usb belleğimize kopyalıyoruz

dd if=2013-02-09-wheezy-raspbian.img of=/dev/sdd

Elimizde bulunan sd karta 1 adet fat32 bölüm oluşturuyoruz (oluşturduğunuz bölüm hem ilk sırada hem de birincil bir bölüm olmalı). Bunu ister Gparted gibi bir araçla yapın isterseniz fdisk kullanın. Bölümleme işlemlerini yaptıktan sonra aşağıdaki komutları uygulayın.

mkdir /tmp/mnt_sd /tmp/mnt_usb
mount /dev/sdd1 /tmp/mnt_usb
mount /dev/sde1 /tmp/mnt_sd

cp /tmp/mnt_usb/* /tmp/mnt_sd

Bu işlemleri yaptıktan sonra /tmp/mnt_sd/cmdline.txt dosyasını düzenlememiz gerekmekte. Bu dosyada root=/dev/mmcblk0p2 olan kısmı root=/dev/sda2 olarak değiştirin.

Kaynak


Bir dosyanın boş olup olmadığını kontrol etme

Shell script yazarken bir dosyanın boş olup olmadığını kontrol etmemiz gerekebilir. Bunu yapmak için birkaç yöntem yazacağım.

satirsayisi=`wc  -l 194.27.108.90  | awk '{split($0,a," "); print a[1]}'`
if [ $satirsayisi == 0 ]; then echo "dosya bos"; fi
if [ -z $(cat dosyaadi) ]; then echo "dosya bos"; fi
if [ -z `cat dosyaadi` ]; then echo "dosya bos"; fi

Ve en güzeli

if [ -s dosyaadi ]; then echo "Dosya dolu"; else echo "Dosya bos"; fi

Subversion sunucu kurulumu

Linux üzerinde kurulumdan bahsedeceğim. Öncelikle kullandığınız dağıtımın paket deposundan subversion paketini kurmalısınız. Ben debian kullandığım için

sudo apt-get install subversion

Kurulum tamamlandıktan sonra gerekli tüm araçlara sahip olmuş oluyoruz.

Bir depo oluşturalım.

svnadmin create depom

depom adli dizinde bir depo oluşturmuş olduk. Bu depo üzerinde gerekli düzenlemeleri yapalım.
depom/conf/svnserve.conf bu dosya temel ayarlarimizi yapacağımız dosya. Bu dosyayı açıp [general] başlığı altındaki ayarları düzenliyoruz. Burdaki tüm ayarlar yorum satırı haline getirişmiş, bunlardan işimize yarayanları aktif hale getirmemiz gerekiyor.

# anon-access = read

Tanımladığımız kullanıcılar dışında kalan kişilerin yetkilerini düzenlemek için kullanıyoruz. Eğer yorum satırı halinde bırakırsak tanımsız kullanıcılar depo üzerinde okuma hakkına sahip oluyorlar. Eğer bu şekilde bırakırsak okuma yapamazlar. Eğer read yerine write yazarsak depomuzda herkes okur-yazar oluyor.

# auth-access = write

Kayıtlı kullanıcıların yetkilerini burdan belirliyoruz. Yorum satırı olmaktan çıkartırsak depomuzda tanımlı kullanıcılar yazma hakkına sahip olur.

# password-db = passwd

Depoya erişim yetkisi verdiğimiz kullanıcıların kullanıcı adı ve parolaların tutulduğu yeri belirtir. Kullanıcıları tanımlamak için yorum satırı halinden çıkartıp istediğimiz yolu belirtmeliyiz, daha sonra ilgili dosyayı düzenlemeliyiz.
Dosya içeriği

[users]
askin = parolam

Parolalar şifrelenmemiş halde tutuluyor.

# authz-db = authz

Kullanıcıların izinlerini daha detaylı olarak ayarlamak istersek bu satırı yorum satırı halinden çıkartıp, istediğimiz bir yolu girmemiz gerekmekte.

# realm = My First Repository

Depomuzun adı, bu satırı yorum satırı olmaktan çıkartıp istediğimiz bir isim veriyoruz.

Tüm bunları yaptıktan sonra depomuzu yayınlayabiliriz. Bunu yapmak için svnserve adında bir programdan yararlanıyoruz.

/usr/bin/svnserve -d -r /home/svn/repositories --log-file /var/log/svnserve

Bu komut /home/svn/repositories dizinindeki tüm depoları yayınlar.

Depoyu chekout etmek için

svn checkout svn://sunucu.com/depom

UDP Broadcast sunucu - istemci

Sunucu:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MY_PORT 3333

int main(int argc, char *argv[])
{

    int sockfd, new_fd;
    struct sockaddr_in my_addr;    // hedef adres
    struct sockaddr_in their_addr; // baglanti yapan adres

    int sin_size, rt;

    // gelen verinin tutuldugu buffer
    char buf[32];

    // islem yapmak icin gerekli dosya gosterici
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    sin_size = sizeof(struct sockaddr_in);

    // adres ile ilgili ayarlar
    my_addr.sin_family      = AF_INET;
    my_addr.sin_port        = htons(MY_PORT); // kullanilacak port
    my_addr.sin_addr.s_addr = INADDR_ANY;     // mevcut ip adresi
    memset(&(my_addr.sin_zero), 0, 8); // geri kalani 0la

    // socket ile dosyayi iliskilendir
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

    while ( 1 ) {
        rt = recvfrom (sockfd, buf, 12, 0, (struct sockaddr *)&their_addr, &sin_size);
        if (rt > 0) {
            printf ("recv: %s\n", buf);
        } else if (rt < 0) {
            printf("Baglanti koptu\n");
            break;
        }
    }

    close(sockfd);
    return 0;
}

İstemci:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define DEST_IP   "255.255.255.255"
#define DEST_PORT 3333

int main(int argc, char *argv[])
{

    int sockfd, new_fd;
    struct sockaddr_in dest_addr; // hedef adres

    int sin_size, rt;
    sin_size = sizeof(struct sockaddr_in);

    // islem yapmak icin gerekli dosya gosterici
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    // adres ile ilgili ayarlar
    dest_addr.sin_family      = AF_INET;
    dest_addr.sin_port        = htons(DEST_PORT);
    dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
    memset(&(dest_addr.sin_zero), 0, 8); // geri kalani 0 la

    // Broadcast icin gerekli yetkiyi al
    int flag = 1;
    if (setsockopt (sockfd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0)
        perror("setsockopt");

    // mesaji gonder
    rt = sendto (sockfd, "slm asl plz", 12, 0, (const struct sockaddr *)&dest_addr, sin_size);
    if ( rt < 0 )
        perror("Hata: ");

    // socketi kapat
    close(sockfd);

    return 0;
}

Mysql Kullanıcı - Database Oluşturma

Herzaman unutup google’a bakacağıma buraya bakayım daha kolay :)

Önce mysql sunucusuna bağlanalım:

mysql -u root -p

Kullanıcı oluşturma:

create user kullaniciadi;

Veritabanı oluşturma:

create database veritabaniadi;

Kullanıcı yetkilerini verme: Tüm yetkiler:

grant all on veritabaniadi.* to kullaniciadi@localhost identified by 'parola';

Sadece okuma (select):

grant select on veritabaniadi.* to kullaniciadi@localhost;

Yetkiler hemen devreye girsin:

flush privileges;