Docker Komut Örnekleri & Hata Giderme

Cem Topkaya
52 min readApr 7, 2020

--

  • Neden Docker?
  • Docker Konteyner Üstünde SSH Anahatarları
  • SSH Kurulumu
  • SSH Anahtar Ayarları
  • Jenkins-Bitbucket Ayarları
  • Private Registry Üstüne Yansı Basamamak
  • http: server gave HTTP response to HTTPS client
  • Var veya Yok, Docker Ayar Dosyası
  • Docker Hizmetini Yeniden Başlatmak
  • WSL’i Tekrar Başlatmak
  • Yansı Oluşturmak veya Kopyalama
  • Yansıyı Kütüğe Basmak
  • Yüklü Yansıları Listelemek (Listing Repos)
  • Yüklü Yansının Etiketlerini (Listing Tags) Listelemek
  • Yerelden Yansıyı Silmek
  • Uzaktaki Docker Kütüğünden Yansıyı Çekmek
  • Private Registry Üstündeki Yansı Silmek (deleting private registry image)
  • nginx daemon on veya daemon off
  • Docker Konteynerden Windows Host’a Erişmek (docker-toolbox)
  • Örnek .dockerignore Dosyası
  • ENV mi ARG mı?
  • ENV
  • ARG
  • ENV vs. ARG
  • RUN mı CMD mi?
  • RUN
  • CMD
  • CMD EZMEK
  • ENTRYPOINT
  • ENTRYPOINT EZMEK
  • Kapsayıcının Başlangıç Komutuyla Ezmek
  • --entrypoint İle Ezmek
  • Yeni Bir Yansıyla Miras Alınan Yansının Entrypoint Bilgisini Ezmek
  • CMD ile ENTRYPOINT Ezmek
  • ADD mi COPY mi?
  • Özetle Ne Zaman ADD veya COPY Kullanmalı?
  • sh, bash, dash, csh, zsh … Ne ola ki?
  • ENTRYPOINT Örneği
  • Volume Mounting (Dizin Bağlama)
  • Konteyner Yaratırken WORKDIR Belirlemek
  • Birden fazla PORT ve VOLUME bağlamak
  • Bir Yansıyı Yeninden Adlandırmak
  • --init
  • Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:
  • /etc/group
  • /etc/passwd
  • Çok satırlı RUN komutu
  • privileged bayrağıyla kapsayıcı başlatmak
  • debconf: delaying package configuration, since apt-utils is not installed
  • sürekli apt update ile yavaşlamamak için multi stage dockerfile
  • apt ile kurulumların non-interactive (sorgusuz)
  • Docker yansısını oluştururken kullanıcı eklemek
  • Konteynerler Arasında İletişim
  • --link ile
  • Özel ağ yaratıp, konteynerleri bu ağda koşturarak
  • multi-stage Dockerfile İçinde ARG tanımlamak
  • build aşamasında argumanların kontrol edilmesi
  • ubuntu:xenial ile Servisleri çalıştırılması
  • Health Check*
  • Health Check ile “docker-compose up” komutunun STATUS CODE bilgisini 0'dan farklı hale getirmek

Neden Docker?

https://medium.com/devopsturkiye/ka%C3%A7%C4%B1rd%C4%B1%C4%9F%C4%B1m%C4%B1z-bir%C5%9Feyler-mi-var-container-mimarisi-tarihi-2a270aa86029

Docker Konteyner Üstünde SSH Anahatarları

Benim Jenkins slave olarak kullandığım konteynerin bitbucket reposuyla bağlantısını ssh anahtarları üstünden halletmem gerekti.

SSH Kurulumu

Linux üstünde ssh kurulu değilse aşağıdaki komutlarla yükleriz yüklüyse doğrudan anahtar ayarlarına geçebiliriz.

$ apt-get update
$ apt-get -y install git

Eğer docker yansısı hazırlıyorsak yansının üstünde ssh kurulumu için Dockerfile şöyle olmalı:

FROM ubuntu RUN apt-get update && \
apt-get -y install git && \
eval `ssh-agent -s` && \
mkdir ~/.ssh \
echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config &&

SSH Anahtar Ayarları

Konteynerin konsoluna bağlanıp, makina üstünde yüklü olan ssh-keygen komutu çalıştırıyoruz.

# ssh-keygen
ssh-keygen
  • Bize anahtarın adı olmazsa /root/.ssh/ dizini içinde id_rsa adıyla bir gizli anahtar yaratacağını söyler
  • Örnek olsun deyyu bir anahtar yaratalım adıda “yeni_anahtar” olsun
  • Sadece yeni oluşturduğumuz anahtarı görmek için | grep ‘anahtar’ diyoruz ve dizindeki dosyaları ls ile listeliyoruz:
  • .pub uzantılı dosya açık (public) anahtarımız ve diğeri gizli (private) anahtarımız oluyor.
  • Oluşturduğumuz anahtarı ssh-agent ile istenildiğinde erişilebilir hale getirmek için ssh-add ile anahtarı ekleyeceğiz. Dikkat etmemiz gereken ssh-agent’ın çalıştığından emin olmak!
Could not open a connection to your authentication agent.
  • ssh-agent bir Process ID almışsa “çalışıyor” demektir.
    Bunun için eval $(ssh-agent) komutunu kullanacağız
eval $(ssh-agent)
  • Artık ssh-add ile anahtarımızı ekleyebiliriz
ssh-add yeni_anahtar

SSH Anahtarı ayarları bittiğine göre tekrar Jenkins-Bitbucket ayarlarına dönebiliriz.

SSH Ayarlarıı görmek için /etc/ssh/ssh_config dosyasına bakabiliriz:

# cat /etc/ssh/ssh_config# This is the ssh client system-wide configuration file.  See
# ssh_config(5) for more information. This file provides defaults for
# users, and the values can be changed in per-user configuration files
# or on the command line.
# Configuration data is parsed as follows:
# 1. command line options
# 2. user-specific file
# 3. system-wide file
# Any configuration value is only changed the first time it is set.
# Thus, host-specific definitions should be at the beginning of the
# configuration file, and defaults at the end.
# Site-wide defaults for some commonly used options. For a comprehensive
# list of available options, their meanings and defaults, please see the
# ssh_config(5) man page.
Host *
# ForwardAgent no
# ForwardX11 no
# ForwardX11Trusted yes
# PasswordAuthentication yes
# HostbasedAuthentication no
# GSSAPIAuthentication no
# GSSAPIDelegateCredentials no
# GSSAPIKeyExchange no
# GSSAPITrustDNS no
# BatchMode no
# CheckHostIP yes
# AddressFamily any
# ConnectTimeout 0
# StrictHostKeyChecking ask
# IdentityFile ~/.ssh/id_rsa
# IdentityFile ~/.ssh/id_dsa
# IdentityFile ~/.ssh/id_ecdsa
# IdentityFile ~/.ssh/id_ed25519
# Port 22
# Protocol 2
# Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
# MACs hmac-md5,hmac-sha1,umac-64@openssh.com
# EscapeChar ~
# Tunnel no
# TunnelDevice any:any
# PermitLocalCommand no
# VisualHostKey no
# ProxyCommand ssh -q -W %h:%p gateway.example.com
# RekeyLimit 1G 1h
SendEnv LANG LC_*
HashKnownHosts yes
GSSAPIAuthentication yes

Jenkins-Bitbucket Ayarları

Sırasıyla gizli, açık anahtarlar oluşunca

  1. açık anahtarı bitbucket adresine tanımlayacak,
  2. Bitbucket kullanıcı adıyla Jenkins üstünde gizli anahtarı, bir credential tanımladıktan sonra
  3. yeni Jenkins görevinde bitbucket repo adresini yarattığınız credential ile ayarlayıp kullanabiliriz
Gizli anahtarı Jenkins üstüne, açık anahtarı bitbucket üstüne
Add Key düğmesiyle yeni bir anahtar giriyoruz
Açık anahtar (public key) bitbucket üstünde tanımlanacak
son boşluktan sonraki kısım ssh anahtarının etiketi oluyor
en son anahtar docker_jenkins_ssh_etiketi olacaktı ama tabi bu görüntü daha önceden çekildi ;)

Sürekli id_rsa anahtarı için şifresini kayıtlı şekilde çalıştırması ve bize şifre sormaması için:

eval ‘ssh-agent -s’ && ssh-add -k /root/.ssh/id_rsa

Referanslar:

Private Registry Üstüne Yansı Basamamak

Get https://192.168.13.33:5000/v2/: http: server gave HTTP response to HTTPS client

Özel docker kayıt kütüğünüze yansınızı (image) göndermek istediniz ve yukarıdaki hata ile karşılaştınız. Özetle durum şudur: docker komutu push argümanını görünce sunucuyla varsayılan ayarlarda aksi belirtilmediği için https iletişimi kurmak istiyor. Ancak kütük (192.168.13.33:5000 adresinde koşan) https konuşmadığı için istek düşüyor.

docker push 192.168.13.33:5000/cinar-gui:0.0.1

Haydi en başa dönelim ve özel kütük nasıl kuruluyor ve biz bu duruma nasıl geliyoruz bakalım.

Docker yansılarınız için kendinize bir registry (kütük, kayıt kütüğü) yaratmak istediniz ve registry yansısını buldunuz.

$ docker run 
-d
-p 5000:5000
--restart always
--name registry
registry:2

-d: Servis olarak çalışsın ve terminalinizi bağlantısız (detached) modda kalsın

-p: Dışarıdan 5000 portuna gelenleri docker konteynerinde 5000 portuna yani docker kütüğünün hizmetini veren uygulamaya yönlendirdiniz

--restart : Docker her tekrar başlatıldığında bu konteyneri de başlatsın diye servisinizi ayarladınız

Artık docker registry niz çalışıyor ve sizden bir yansıyı push etmenizi bekliyor

$ docker pull ubuntu
$ docker tag ubuntu localhost:5000/ubuntu
$ docker push localhost:5000/ubuntu

Önce ubuntu yansısını çekelim (pull) bilgisayarımıza

Sonra farklı bir etiketle (tag) isimlendirelim.

$ docker tag <kopyalanacak_yansının_tam_adı_veya_idsi> <REGISTRY_HOST>:<REGISTRY_PORT>/<yansinin_yeni_adi>:<varsa_versiyon>

REGISTRY_HOST: Yansı kütüğümüzün çalıştığı makinanın adını/ipsi. Buradaki bilgiye bakarak kopyalanacak yansıyı hangi kütüğe basacağını anlayacak docker komut satır uygulaması. 192.168.13.33 IP’li bir makinada docker kütüğümüz çalıştığı için bu IP adresini kendi örneğimde kullanacağım.

REGISTRY_PORT:5000 Portunda varsayılan olarak çalışıyor registry. Ama daha önemlisi biz konteyneri çalıştırırken yukarıda -p 5000:5000 demiştik. Kalın yazıyla olan dışarıdan gelen port olduğu için biz buradaki değeri Registry_Port olarak kullanacağız.

Hatanın olduğu yere geliyoruz. Docker kütük hazır. Şimdi herhangi bir docker istemcisinden bu sunucuya yansı basmaya geldik.

http: server gave HTTP response to HTTPS client

docker push 192.168.13.33:5000/cinar/nf:latest

Aldığım hata diyor ki;

bağlanmak istediğin repoya https ile bağlantı kurmak gerekiyor çünkü ben varsayılan olarak docker yansı kütüklerine https giderim. Heyhat! Bir baktım senin repona https ne mümkün!

Bunu gözlemlemek için docker info komutunu kullanabiliriz:

root@CEM-TOPKAYA-PC:/etc/docker# ll
drwxr-xr-x 2 root root 4096 Oct 22 17:55 ./
drwxr-xr-x 98 root root 4096 Oct 22 17:51 ../
-rw-r--r-- 1 root root 53 Oct 22 17:55 daemon.json
-rw------- 1 root root 244 Oct 22 17:43 key.json
root@CEM-TOPKAYA-PC:/etc/docker# cat daemon.json
{
"insecure-registries" : ["192.168.13.33:5000"]
}
root@CEM-TOPKAYA-PC:/etc/docker# service docker restart
* Stopping Docker: docker [ OK ]
* Starting Docker: docker [ OK ]
root@CEM-TOPKAYA-PC:/etc/docker# docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 18.09.7
.
..
...
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
192.168.13.33:5000
127.0.0.0/8
Live Restore Enabled: false
The push refers to repository [192.168.13.33:5000/cinar/nf]
Get https://192.168.13.33:5000/v2/: http: server gave HTTP response to HTTPS client

Bu yüzden docker istemcisine diyeceğiz ki; bu docker repo http ile bağlantı kurulabilir yani güvensiz iletişim sağlayabileceğin bir sunucudur. Bu bilgiyi docker’ın kullandığı daemon.json dosyasında saklayacağız.

Var veya Yok, Docker Ayar Dosyası

Windows içindeki docker için C:\ProgramData\Docker\config\daemon.json dosya yolu kullanılabilir.

The preferred method for configuring the Docker Engine on Windows is using a configuration file. The configuration file can be found at ‘C:\ProgramData\Docker\config\daemon.json’. You can create this file if it doesn’t already exist.
docs.microsoft.com

Bu dosya yoksa bile yaratacak ve docker servisini tekrar başlatacağız. Başlattıktan sonra durumuna bakın, hata olmuş mu deyu.

$ vi /etc/docker/daemon.json

Bu dosyada en azından olması gereken bilgi “insecure-registries” özelliğidir. http ile docker komutlarının iletişim kurabilmesi için aşağıdaki gibi bir içeriğin olması gerek.

{
"insecure-registries" : ["192.168.13.33:5000"]
}

Tüm daemon.json dosyası şöyle olabilir:

Docker Hizmetini Yeniden Başlatmak

$ systemctl restart docker
systemctl restart docker && systemctl status docker

WSL’i Tekrar Başlatmak

Private Registry Üstündeki Yansı Silmek (deleting private registry image)

Eğer WSL içindeki docker için bu değişikliği yaptıysanız ve WSL’i tekrar başlatmak isterseniz powershell’i administrator ile açarak Restart-Service LxssManager komutunu çalıştırabilirsiniz.

Restart-Service LxssManager

Ref: https://superuser.com/a/1347725/38133

Yansı Oluşturmak veya Kopyalamak

Private Registry Üstündeki Yansı Silmek (deleting private registry image)

  1. Bir yansıyı Dockerfile dosyasından yaratmak için:
$ docker build -t 192.168.13.33:5000/cinar-gui:0.0.1 .

. İle bulunduğum dizinde Dockerfile arayacak ve bulunca derleyecek ve 192.168.13.33:5000/cinar-gui:0.0.1 etiketli (-t) bir yansı yapılandıracak (build)

2. Var olan bir yansıyı kopyalamak için:

Yukarıda detaylı olarak anlatılmıştı sadece örnek satır şöyle olur:

$ docker tag cinar-gui 192.168.13.33:5000/cinar-gui:0.0.1

Yansıyı Kütüğe Basmak

$ docker push 192.168.13.33:5000/cinar-gui:0.0.1

Yüklü Yansıları Listelemek (Listing Repos)

http://192.168.13.33:5000/v2/_catalog
$ docker inspect registry_new | grep '"Ports"' -A5
$ curl -X GET localhost:5000/v2/_catalog
$ curl -X GET localhost:5000/v2/_catalog | json_pp -json_opt pretty

Yüklü Yansının Etiketlerini (Listing Tags) Listelemek

GET /v2/<name>/tags/list
http://192.168.13.33:5000/v2/cem%2Fcinar-web/tags/list
http://192.168.13.33:5000/v2/_catalog
http://192.168.13.33:5000/v2/_catalog
http://192.168.13.33:5000/v2/ulak%2Fcinar-web/tags/list
// indirmek için
$ docker pull 192.168.13.33:5000/ulak/cinar-web:0.5.15-b167
// yansıları listelemek
$ docker image ls
// çalıştırmak için
$ docker run -d
--name=cinar_web
-p 81:80
2aab (docker yansısının id değeri)

Yerelden Yansıyı Silmek

Artık yansıyı docker kütüğüne bastığımıza göre, yereldeki yansıdan kurtulabiliriz:

docker image rm 192.168.13.33:5000/cinar-gui:0.0.1

Tekrar listeyi aldığımızda yerelden gittiğini görürüz. Tekrar kataloğa baktığımızda uzaktaki docker kütüğünde listeleyeceğiz.

Uzaktaki Docker Kütüğünden Yansıyı Çekmek

Private Registry Üstündeki Yansı Silmek (deleting private registry image)

  1. Yansı isimlerini listele ve içinden birini seç
  2. Etiketleri listele ve içinden birinin digest bilgisini getir
  3. Yansı adı ve digest bilgisiyle siliyoruz

Komut satırında aşağıdaki iki komut da aynı çıktıyı verir:

$ curl --request GET --url http://192.168.13.33:5000/v2/_catalog
{"repositories":["cinar/nf","cinar-gui","malpine","ulak/cinar-chf-sto","ulak/cinar-chf-vto","ulak/cinar-web"]}
$ curl -X GET http://192.168.13.33:5000/v2/_catalog
{"repositories":["cinar/nf","cinar-gui","malpine","ulak/cinar-chf-sto","ulak/cinar-chf-vto","ulak/cinar-web"]}
veya private docker registry'nin olduğu host makinede$ curl -X GET http://localhost:5000/v2/_catalog
{"repositories":["cinar/nf","cinar-gui","malpine","ulak/cinar-chf-sto","ulak/cinar-chf-vto","ulak/cinar-web"]}
curl --request GET --url http://192.168.13.33:5000/v2/_catalog

Hangi yansı adını (name) seçeceğimize karar verdikten sonra sırada etiketlerini (tag) getirmek

$ curl http://192.168.13.33:5000/v2/ulak/cinar-web/tags/listveya private docker registry'nin olduğu host makinede$ curl localhost:5000/v2/ulak/cinar-web/tags/list
curl -X GET http://192.168.13.33:5000/v2/ulak/cinar-web/tags/list

Etiketin bilgilerini görmek için çekelim ama Digest bilgisini bir sonrakinde alacağız:

$ curl http://192.168.13.33:5000/v2/ulak/cinar-web/manifests/0.9.0-b440-STOveya private docker registry'nin olduğu host makinede$ curl localhost:5000/v2/ulak/cinar-web/manifests/0.9.0-b440-STO

Şimdi digest almak için başlık bilgisine şunu ekleyelim:

Accept: application/vnd.docker.distribution.manifest.v2+json
Shell komutumuz şu oluyor:
$ curl --request GET \
--url http://192.168.13.33:5000/v2/ulak/cinar-web/manifests/0.9.0-b440-STO \
--header 'Accept: application/vnd.docker.distribution.manifest.v2+json'
veya private docker registry'nin olduğu host makinede$ curl localhost:5000/v2/ulak/cinar-web/manifests/0.9.0-b440-STO -H 'Accept: application/vnd.docker.distribution.manifest.v2+json'
curl localhost:5000/v2/ulak/cinar-web/manifests/0.9.0-b440-STO -H ‘Accept: application/vnd.docker.distribution.manifest.v2+json’

Artık digest bilgisine de eriştiğimize göre silebiliriz:

curl -X DELETE localhost:5000/v2/ulak/cinar-web/manifests/sha256:1eb39607769f3f571960357c1742d573a4804fa615b4aedee7156e00ba6ca8dc
const fetch = require('node-fetch');
const baseUrl = 'http://192.168.13.33:5000'
var fetchTags = function (name) {let url = baseUrl + '/v2/' + name + '/tags/list';let options = { method: 'GET' };return fetch(url, options)
.then(res => res.json())
.then(res => {
console.log(res.tags.length)
return res.tags
})
}
var fetchDigest = function (name, tag) {let url = baseUrl + '/v2/' + name + '/manifests/' + tag;let options = {
method: 'GET',
headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }
};
return fetch(url, options)
.then(res => res.json())
}
var deleteImage = function (name, digest) {let url = baseUrl + '/v2/' + name + '/manifests/' + digest;let options = { method: 'DELETE' };return fetch(url, options)
.then(res => res.status)
}
var name = 'ulak/cinar-web'
fetchTags(name)
.then(tags => tags.sort().slice(600, 700))
// .then(console.log)
.then(arr => Promise.all(arr.map(t => fetchDigest(name, t))))
.then(arr => arr.map(c => c.config.digest))
.then(digests => Promise.all(digests.map(d => deleteImage(name, d))))
.then(console.log)
.catch(err => console.error('error:' + err));

Private Registry Üstündeki Kullanılmayan Yansıları Silmek

registry_new ismi bizim kullandığımız özel kütüğün docker adı.

$ docker exec registry_new bin/registry garbage-collect --dry-run /etc/docker/registry/config.yml

nginx daemon on veya daemon off

Nginx daemon off veya on ile arkada veya önde uygulamanın çalışmasını sağlıyor. Docker içinde bir nginx çalıştırıyorsak ve konteyner’den çıkmasını istemiyorsak daemon off; anahtarıyla çalıştırarak ön planda nginx uygulamasının çalışmasını sağlayacağız.

Ref: https://stackoverflow.com/a/34821579/104085

on Yansısına interaktif bağlandığımızda otomatik çıkış yaptığı, off da ise askıda kaldığını görüyoruz

--rm Anahtarıyla başlattığımız konteyner işi bitince çıkacak ve konteyner silinecek.

Aşağıdaki ekran görüntüsü nihayetinde sadece off olan konteyner’in sistemde olduğunu görüyoruz

-d anahtarı docker konteynerini daemon (servis gibi bir şey) modunda çalıştırır, konsolu özgür bırakır
# https://www.katacoda.com/courses/docker/2
FROM nginx:1.11-alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
# 1. bu yansıdan bir konteyner yarattığımızda (bir sınıfın örneğini oluşturur gibi imajın bir örneğini konteyner olarak örneklediğimizde)
# 2. 100 CMD olsa bile sonuncusu çalıştırılarak, içindeki nginx programı "-g daemon off;" parametreleriyle başlatılacak
# 3. "daeomon on" deseydik arka planda çalışacak ve "docker run" tıpkı bir main metodunun sonuna gelip çıkması gibi bitecekti
CMD ["nginx", "-g", "daemon off;"]

Dockerfile dosyasından yansı oluşturmak için docker build -t yansıAdı . komutunu çalıştırıyoruz. Ekran görüntüsünde yansının adını off demişiz

Port Bağlama

Dış dünyaya 80 portundan hizmet vereceğini EXPOSE 80 ile belirtiyoruz. Ancak konteyneri çalıştırırken 4200 portuyla eşleştireceğiz. Ve bu port üstünden talep ettiğimizde sonucu alabildiğimizi görüyoruz.

Docker Konteynerden Windows Host’a Erişmek (docker-toolbox)

VirtualBox üstünden hangi ağ arayüzlerinin olduğunu görmek için Anamakine Ağ Yöneticisi sekmesini açalım ve ana makinamızın olduğu ağ üstünde windows makinamızın IP adresini 192.168.56.1 olarak görürüz. Artık docker konteyner içinden bu adresle windows host makinasına erişebiliriz tek bir detayla: “windows host üstünde ilgili portu tüm ağ arayüzlerinden veya 56.1 arayüzünden dinleyen bir uygulama varsa”.

Biz verdaccio sunucusunu ayaklandırıyor ve 0.0.0.0 portunu dinleyecek şekilde config.yaml dosyasında güncelleme yapıyoruz. Şimdi docker konteyner üstünden bağlantı testi:

Örnek .dockerignore Dosyası

COPY Talimatı .dockerignore dosyasında tanımlı dosyaları kopyalamaz!

node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode

ENV mi ARG mı?

Her ikisi de değişkenleri tanımlamanıza yardımcı olan Dockerfile talimatlarıdır ve Dockerfile içinde oldukça benzer kullanılırlar.

ENV ileride çalışan konteynerler içindir.

ARG ise Docker görüntünüzü oluşturmak içindir.

ENV

ENV temel olarak gelecekteki ortam değişkenleriniz için varsayılan değerler sağlar. Kapsayıcılar içinde kullanılacağından CMD ve ENTRYPOINT komutlarında kullanabilirsiniz.

Dockerlaştırılmış uygulamalar, ortam değişkenlerine erişebilir. Örneğin bir NodeJs uygulamanızı dockerlaştırdınız. Uygulama development veya production modunda çalışsın istiyorsunuz ve bu değeri projenize geçirmek istiyorsanız ENV harika bir yoludur.

ARG

ARG ise Dockerfile yapılandırılırken (docker build --build-args arg=deger ile yapılandırılırken yani yansınız oluşturulurken) kullanacağınız değişkenlerinize erişmenizi sağlar.

ARG değişkenlerinize yapılandırmada (docker build) girdiğiniz değerleri görüntü oluşturulduktan sonra (docker run gibi komutlarda) kullanamazsınız. Çalışan bir konteyner bir ARG değişken değerine erişemez. Yani ARG değişkenlerini çalışma zamanı konteyner komutlarında (CMD, ENTRYPOINT vs) kullanamazsınız.

ENV vs. ARG

  • ARG değişkenlerini Dockerfile kullanır.
  • ENV değişkenlerini Konteyner kullanır.
  • ARG değişkenleri build sırasında kullanılır
    (docker build --build-arg key=val).
  • ENV değişkenleri run sırasında kullanılır (docker run -e "key=val").
  • ARG değişkenlerine docker run esnasında erişilemez ve değer atanamaz!
  • ENV değişkenlerine docker run sırasında değer atayarak varsayılan değeri ezebiliriz. docker build sırasında değer atanamaz! Ancak ENV değişkenine Dockerfile içinde bir ARG değişkenini değer olarak atarsak varsayılan değeri build esnasında değiştirilebilinir.
# Varsayılan değeri 5 olan degiskenArg, yansıyı yaratırken 
# docker build --build-arg degiskenArg=55
# komutuyla değeri değiştirilebilinir
ARG degiskenArg 5# degiskenEnv için varsayılan değer degiskenArg olduğu için yansı
# yapılandırılırken komut satırından atanan yeni bir değerle
# ezilmemişse degiskenArg'ın 5 değerini alır.
# Komut satırında degiskenArg için 55 değeri atanırsa degiskenEnv
# için yeni değer yapılandırma zamanında 55 olur
ENV degiskenEnv $degiskenArg

Docker-compose.yml

services:
node:
build:
args:
degiskenArg:5
environment:
- "degiskenEnv=6"
  • Her ikisine de Dockerfile içinde varsayılan değer atanabilir
ARG degiskenArg 321
ENV degiskenEnv 123
  • Her ikisine de $DegiskenAdi diyerek Dockerfile içinde erişilebilir
  • Her ikisini de RUN komutundan bakarak ENV mi ARG mı diye anlayamayız! Tanımlandıkları satırlara bakmamız gerekir.

Refs:

RUN mı CMD mi?

RUN

RUN ile yansının içinde bir komut çalıştırmak için kullanılır.

İstediğimiz kadar RUN komutunu Dockerfile içinde kullanabiliriz.

Mesela;

  1. Yansımıza kurulum yapmak için wget’i kuracağız
  2. Nginx’i web sunucu olarak kullanalım
  3. NodeJS’i de sunucu tarafındaki kod için
  4. Kurulum bittikten sonra yansımızı küçük tutmak için wget’i kaldıralım
  5. E tabiki bir de linux sürümüne ihtiyacımız olacak

Ancak ilk 4 madde için Dockerfile içinde RUN komutunu epeyce kullanmamız gerekiyor.

Her RUN komutu sonucu bir hash ile katman oluşturularak depolanır. Örneğin RUN cp ./x_dosyasi.sh ./y_dizini/x_dosyasi.sh olarak kopyaladığımızda bir katman oluşur ancak devamında bu dosyayı aynı RUN içinde silerseniz bu katmanı hiç yaşanmamış gibi siler.

RUN cp ./x_dosyasi.sh ./y_dizini/x_dosyasi.sh && \
rm ./y_dizini/x_dosyasi.sh

CMD

RUN komutu docker build komutuyla birlikte yansı (image) oluşturulurken çalışır. Yani image içinde ,

CMD komutu, bir docker kapsayıcısı başlatıldığında yürütülecek komutu belirtir.

CMD komutunun temel amacı, bir konteynerde gerekli olan yazılımı başlatmaktır. Örneğin, konteyner başlar başlamaz kullanıcının yürütülebilir bir (exe gibi) dosyasını veya bir Bash terminalini çalıştırması gerekebilir bu tür istekleri işlemek için CMD komutu kullanılabilir.

Prensip olarak, Dockerfile dosyasında yalnızca bir CMD komutu olmalıdır. CMD birden çok kez kullanıldığında, yalnızca son örnek yürütülür.

Aşağıdaki gibi CMD talimatı verilebilir:

CMD executable parameter1 parameter2

Dizi içinde vermek iyi bir pratiktir:

CMD ["executable", "parameter1", "parameter2"]

Dockerfile içinde:

FROM yansı_adı
ARG hede
ARG hadi
CMD ["sh", "-c", "çalış ${hede} ${hodo}"]
------------------------------------------
$ docker build --build-arg hede=hodo hadi=nire .

Bir CMD komutu, docker run komutunda yürütülebilir dosya ve parametreleri sağlayarak geçersiz kılınabilir.

docker run <yansı_adı> executable parameters

Dockerfile içindeki tüm CMD’leri geçersiz kılar.

Birçok geliştirici CMD’yi ENTRYPOINT ile karıştırmaktadır. Ancak, ENTRYPOINT docker run tarafından geçersiz kılınamaz. Bunun yerine, docker run ile verilen parametreler ENTRYPOINT’e eklenecektir — CMD’de durum böyle değildir!

CMD EZMEK

Kalıbın (yansı, image ne derseniz artık) CMD ile belirtilen çalıştırılacak komutunu kap (container, kapsayıcı ne derseniz artık) oluşturulurken ezilebilir.

# cat Dockerfile 
FROM ubuntu
MAINTAINER sofija
RUN apt-get update
CMD ["echo", "Hello World"]
# docker build -t deneme/cmd:1 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM ubuntu
---> d70eaf7277ea
Step 2/4 : MAINTAINER sofija
---> Using cache
---> 885a353cf14d
Step 3/4 : RUN apt-get update
---> Using cache
---> 52fb63df86a6
Step 4/4 : CMD ["echo", "Hello World"]
---> Using cache
---> a305188c034a
Successfully built a305188c034a
Successfully tagged deneme/cmd:1
# docker images | grep cmd
deneme/cmd 1 a305188c034a 7 minutes ago 98.8MB
# docker run --name c-cmd-1 deneme/cmd:1
Hello World
# docker ps -a --no-trunc --format "{{.Names}} \t {{.Command}}" | grep cmd
c-cmd-1 "echo 'Hello World'"
# docker run --name c-cmd-2 deneme/cmd:1 echo "Selam Dunyali"
Selam Dunyali
# docker ps -a --no-trunc --format "{{.Names}} \t {{.Command}}" | grep cmd
c-cmd-2 "echo 'Selam Dunyali'"
c-cmd-1 "echo 'Hello World'"
# docker inspect c-cmd-1 | jq .[0].Config | jq -c --tab
# docker inspect c-cmd-2 | jq .[0].Config | jq -c --tab

ENTRYPOINT

1. ENTRYPOINT Örneği

Etiketler: ENTRYPOINT, sh, $@, $1, $2, build

sh Dosyasını batch dosyası gibi düşünebiliriz. Parametre alıp basit bir komut yazımıyla işler yaptırabiliriz. Aşağıdaki örnekteki gibi:

https://stackoverflow.com/a/3536154/104085

Bir konteyner’in ENTRYPOINT değerini ezmek için --entrypoint argumanını kullanırız. Aşağıdaki ekran çıktısında eğer konteyner yansısı yapılandırılırken shell yüklenmişse çalıştırmak için --entrypoint=sh kullanırız. Eğer shell yüklenmemişse hata alırız (bkz. hello-world yansısına)

docker run — rm -it --entrypoint=sh node

Şimdi giris.sh adında bir dosya yaratıp Dockerfile içinde ENTRYPOINT olarak belirleyerek konteyner başladığında çalışmasını sağlayalım.

----------- Dockerfile --------------------------------------------
-- ENTRYPOINT için bir *.sh dosyası alacak
-- ve konteynere geçirilen parametreler bu sh dosyasına geçirilecek
--------------------------------------------------------------------
$ cat Dockerfile FROM ubuntu COPY ./giris.sh ./ ENTRYPOINT ["./giris.sh"]
----------- giris.sh ----------------------------------------------
-- sh dosyasına geçirilen tüm parametreler $@ ile alınır
-- ayrı ayrı parametreleri almak için $n kullanılır
--
-- echo "First arg: $1"
-- echo "Second arg: $2"
-- echo "List of all arg: $@"
--
-- she-bang(#!)
--
--------------------------------------------------------------------
$ cat giris.sh #!/bin/bash echo selamlar "$@", naber?
----------- Yansımızı build edelim ---------------------------------
-- Yansının bir etiketi olsun diye -t kullanıyoruz
-- bulunduğumuz dizindeki Dockerfile dosyasını
-- işaret etmek için . kullanıyoruz
--------------------------------------------------------------------
$ docker build -t cemt/tuts-entrypoint:1 . Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM ubuntu
---> 4e5021d210f6
Step 2/3 : COPY ./giris.sh ./
---> bba1077267b0
Step 3/3 : ENTRYPOINT ["./giris.sh"]
---> Running in c8204dab0d92
Removing intermediate container c8204dab0d92
---> b54aea9c3994
Successfully built b54aea9c3994
Successfully tagged cemt/tuts-entrypoint:1
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
----------- Konteyner imalatına geçelim ---------------------------
-- konteyner oluşturmak & çalıştırmak için run anahtarını,
-- konteyner exit olunca otomatik kaldırılsın diye --rm anahtarını,
-- konteynerin adı olsun diye (ki siliyoruz! olmasa da olur) --name,
-- terminali bağlayıp interactive olsun diye -it (ki gerek yok!)
-- konteyner oluşturacağımız yansının adını,
-- ve ENTRYPOINT'e arguman geçirmek için Cemo
-- parametrelerini docker CLI'a geçiriyoruz
--------------------------------------------------------------------
$ docker run --rm -it --name=hede cemt/tuts-entrypoint:1 Cemo selamlar Cemo, naber?

Birazda ARG ile örneği tekrar yazalım:

----- Kabuk Betiği Dosyamız -------------------------------------
$@ ile tüm parametreleri
$1 ile ilk parametreyi
$2 ile ikinci parametreyi alıyoruz
------------------------------------------------------------------
$ cat giris.sh
#!/bin/bash
echo selamlar $2, $1
echo naber?
----- Dockerfile Dosyamız -----------------------------------------
-- Konteyneri çalıştırmak için aşağıdaki komut çalışacak
-- docker run --rm cemt/tuts-entrypoint:2 cEm
--
yansı adından sonra gelenler parametre olarak konteynere
-- aktarılacak.
-- Eğer CMD içinde ARG değişkenlerini kullanamayız (ARG değişkenleri Dockerfile içinde build zamanında kullanılabilir)! Kapsayıcı zamanında çalışacağı için ENV değişkenlerini kullanabiliriz.
-- ENTRYPOINT için giris.sh dosyası çalışır ve $AD, $SOYAD değişkenleri parametre olarak geçirilir.
--------------------------------------------------------------------
$ cat Dockerfile
FROM ubuntu
COPY ./giris.sh ./
ARG ISIM="İsimsiz Kişi"
ENV AD=$ISIM
ENV SOYAD="Soyisimsiz Varlık"
ENTRYPOINT ./giris.sh $AD $SOYAD----- BUILD Edelim -----------------------------------------------
$ docker build -t yansi --build-arg ISIM="veli" --no-cache .
----- 2 Parametreyi de Verelim -------------------------------------
$ docker run --rm -e SOYAD=gocer yansi
selamlar gocer, veli naber?
----- Hiç Parametre Vermeyelim -------------------------------------
$ docker run --rm yansi
selamlar Soyisimsiz, veli naber?

2. ENTRYPOINT Örneği

Dockerfile içindeki ENTRYPOINT [“echo”, “Hello World”] satırı kapsayıcının çalıştırıldığında çalıştırdığı komut olacak.

# cat > Dockerfile << EOT
FROM ubuntu
MAINTAINER sofija
RUN apt-get update
ENTRYPOINT ["echo", "Hello World"]
EOT
# docker build -t deneme/entry:1 .
# docker images | grep deneme
# docker run --name c-entry-1 deneme/entry:1
# docker ps -a | grep entry

ENTRYPOINT EZMEK

Kapsayıcının Başlangıç Komutuyla Ezmek

Şimdi kapsayıcının başlangıç komutunu docker run ile ezmeye çalışalım:

# docker ps -a --format "{{.Names}} \t {{.Command}}" | grep entry
c-entry-1 "echo 'Hello World'"
# docker run --name c-entry-2 deneme/entry:1 yok yok dunya
Hello World yok yok dunya
# docker ps -a --no-trunc --format "{{.Names}} \t {{.Command}}"| grep entry
c-entry-2 "echo 'Hello World' yok yok dunya"
c-entry-1 "echo 'Hello World'"

--entrypoint İle Ezmek

--entrypoint argumanıyla entrypoint bilgisini kapsayıcıda ezebiliriz.

Dockerfile
FROM ubuntu
MAINTAINER sofija
RUN apt-get update
ENTRYPOINT [“echo”, “Hello World”]

Kapsayıcı Oluştururken (CMD gibi ENTRYPOINT’e ekle)
# docker run --name=c-entry-2 deneme/entry:1 yok yok dunya

Kapsayıcı Oluştururken ( --entrypoint flag ile ezmek)
docker run --name=c-entry-epoint --entrypoint=/bin/bash deneme/entry:1 -c ls

Yeni Bir Yansıyla Miras Alınan Yansının Entrypoint Bilgisini Ezmek

Yeni bir yansı oluşturuyorsanız FROM xxx ile miras aldığınız yansının Entrypoint bilgisini eze. Mesela A yansısından bir B yansısı oluşturdunuz:

Önce bir A yansımız olsun:

FROM ubuntu
ENTRYPOINT ["which", "sh"]
# docker build -t a-yansisi .
docker inspect aaa

Sonra B yansımız:

FROM a-yansisi
ENTRYPOINT ["echo", "beni kimse ezemez"]
# docker build -t b-yansisi

CMD ile ENTRYPOINT Ezmek

Şimdi CMD ve ENTRYPOINT nasıl birleştiriliyor ve en son ENTRYPOINT komutu kapsayıcı içinde çalıştırılıyor ona bakalım:

ENTRYPOINT esas alınıyor CMD ise parametre gibi ENTRPOINT komutuna ekleniyor. Yani CMD ile ENTRYPOINT ezilemiyor ve yine çalışan ENTRYPOINT oluyor. Eğer CMD içeriği ENTRYPOINT ile uyumlu değilse ENTRYPOINT komutu da çatlıyor.

ADD mi COPY mi?

COPY ve ADD, benzer amaçlara hizmet eden Dockerfile talimatlarıdır. Dosyaları belirli bir konumdan Docker görüntüsüne kopyalamanızı sağlar.

COPY yerel bir dosya/dizindeki kaynağı, hedefe kopyalar.

ADD yerel bir dosya/dizini VEYA URL’yi kaynak olarak alır ve hedefe kopyalar. Ayrıca bir tar dosyasını kaynaktan doğrudan hedefe çıkarabilirsiniz.

Çoğu durumda bir URL kullanıyorsanız, bir zip dosyası indiriyorsunuz ve daha sonra onu ayıklamak için RUN komutunu kullanıyorsunuz. Ancak, burada ADD yerine curl ile RUN’u da kullanabilirsiniz, böylece daha küçük bir Docker görüntüsü oluşturmak için her şeyi 1 RUN komutuna zincirlersiniz.

Özetle Ne Zaman ADD veya COPY Kullanmalı?

ADD için geçerli bir kullanım örneği, yerel bir tar dosyasını Docker görüntünüzdeki belirli bir dizine ayıklamak istediğiniz zamandır.

Yerel dosyaları Docker görüntünüze kopyalıyorsanız, daha açık olduğu için her zaman COPY kullanın.

sh, bash, dash, csh, zsh … Ne ola ki?

Bir docker container içinde sh, bash, dash ile bağlanmak istediğimizde.

Not: Yukarıdaki docker container bir linux sürümünün üstüne kurulmuş ancak içinde sadece bash, sh, dash mevcutken, csh, zsh… gibi kabuk uygulamaları yüklü değil.

Öncelikle işletim sisteminizi (linux tabi) varsayılan SHELL’i nedir diye kontrol etmek için:

$ echo "$SHELL ($0)"
VEYA
$ echo $0
VEYA
$ ps -p $$

Yüklü SHELL listesini görmek için:

$ cat /etc/shells
$ cat /etc/shells

Ref: cyberciti.biz

Şimdi shell nedir öğrenelim…

Shell, işletim sistemlerinde işlerimizi kolaylaştırmak/otomatikleştirmek için standartlaştırılmış komutlar şartnamesidir (specification).

Shell (kabuk) işletim sistemleri için bir komut dili yorumlayıcısıdır. Bir komut dilinin nasıl olması gerektiğini tarif eder (POSIX -Portable Operating System Interface- şartnameleri bunun içindir).

ksh88, dash, csh, zsh, ksh, bash … Gibi birçok uygulaması vardır.

bash, Shell’in bir uygulaması olarak düşünülebiliriz. Stephen R. Bourne tarafından geliştirilmiştir.

Stephen R. Bourne (ingiliz üstat)

dash, Tıpkı bash gibi ama daha az yer kaplayan, daha az sayıda kütüphane içeren, hızlı kabuk uygulamasıdır(POSIX 1003.2 şartnamesine göre düzenlenmiştir). Bu yüzden Debian ve Ubuntu bash yerine artık dash kullanırlar.

Özetle; sh bir uygulama değil, bir spesifikasyon olduğundan, /bin/sh çoğu POSIX sisteminde gerçek bir uygulamaya yönelik sembolik bağlantıdır. Yani /bin/sh komutunu çalıştırdığımızda sistemde yüklü ve sh’ın uygulamalarından “bash, dash, ksh88, csh, zsh, …” gibi bir kabuk uygulamasını çalıştırır.

Karakterlerin nelere karşılık geleceğini, rezerve kelimeleri vs. tayin etmiştir.

Bir işletim sisteminde çeşitli işlerimizi kolayca kodlayabileceğimiz bir scripting kullanırız. Bu script dosyalarını program gibi çalıştırdığımız için yorum satırı olduğunu gösteren # ile başlayan satırlar ekleriz. # Karakterini gören yorumlayıcı hemen altındaki satırdan devam eder. İlk satıra she-bang diye adlandırılan #! karakterleri istisnadır ve ardından hangi kabuk uygulamasına denk geldiğini gösteren dosya yolu gelir. Böylece scripti doğrudan ilgili shell uygulamasına yönlendirerek derlemesini sağlar.

#!/bin/bash 
# kodlamalar hoşluklar vs.

Veya

#!/bin/sh
--------
#!/bin/csh -f
# C shell veya uyumlu bir shell uygulamasını çağırır
--------
#!/usr/bin/env python
--------
#!/usr/bin/perl -T
--------

gibi gibi

Refs:

Volume Mounting (Dizin Bağlama)

Dizin bağlamak için -v Host_Makinadaki_Dizin_Yolu:Dockerdaki_Dizin_Yolu şeklinde argümanı docker cli içinde kullanmamız gerekecek.

Çoook dikkat ediniz; eğer bağlamak istediğiniz dizin VirtualBox içinde (Docker Toolbox kullanıyorsanız Hypervisor’ünüz VirtualBox’tır) sanal makinaya bağlanmış olması gerekir! Aksi halde host makinanızdaki dizinin içeriğine Docker Konteyneriniz erişemez çünkü boş bir dizin olarak bağlamış olursunuz!

Eğer c:\Users veya c:\docker klasörü haricindeki bir dizini bağlamak istersem boş dizin görürüm.

Adım adım aşağıdaki komutlarla ne yapmak istediğimizi inceleyelim.

$ docker rm -f \
$(docker ps -a | awk '/cem/ {print $1}') || true \
&& \
docker run --name=cem \
-p 81:3000 \
-v $(pwd):/var/www \
-w /var/www \
node:12 \
ls -l

İnceleme Başlasın

  • Tek satırda, birden fazla komut çalıştırarak iş görmek için komut1 && komut2 şeklinde komutları && ile bağlayacağım.
$ echo "bu bir" && echo "bu iki"
bu bir
bu iki

Eğer birinci komut hata üretirse (exit 1 mesela) ikinci komut çalıştırılamaz!

  • docker ps -a ile çalışmış veya çalışan tüm konteynerlerin bilgisini görüntülemek istiyorum.
docker ps -a
  • Bu çıktının içinde cem geçen satırları awk ile bulup satırdaki ilk kelimelerini (konteyner ID ye denk gelir) yazdıracağım
  • cem isimli konteynerleri sil” demek istiyorum. Ama eğer cem ile eşleşen bir konteyner satır bilgisi yoksa docker rm -f komutuna parametre geçiremeyiz ve sonraki komutu çalıştıramayız!

Tekrar Hatırlayalım: Eğer varsa konteyner silinsin ve tekrar aynı isimde yaratılsın istiyoruz.

İşte bu yüzden komutu || true ile bağlayarak, hata üreterek çıkış yapmasının önüne geçiyoruz. Biraz daha inceleyelim mi?

Özetle:

Dizinin içindekileri görüntülemek için dir komutunu çalıştırıp peşine ekrana echo hedeHodo yazacağız. Yani iki komutu peşi sıra çalıştıracağız. Ama zincirde herhangi bir komut hatayla sonlanırsa peşinden gelecek olan komutlar çalıştırılamaz. Bu yüzden hataya neden olabilecek komutları VEYALIYORUZ (tarzanca OR’lamak) yani || true ile hatasız çıkışı sağlıyoruz. Örn: if( 1==2 || true) komutunu ele alalım. Ya da siz ele alın ben devam edeyim.

dirt diye bir komut olmadığı için exit 1 olur ve 2. komut çalıştırılmaz

Detaylı:

AWK için bir başlık altında not tutuyordum bakabilirsiniz. Özetle satırın sonunda olacak ($) ve bir boşlukla ( ) başlayacak cem kelimesi olacak konteynerin ( cem$), eşleştiği satırdaki ilk kelimeyi ($1) yani konteyner ID sini docker rm -f komutuna parametre olarak geçir ve konteyneri kaldır/uçur/yok et/sil. Ama bu isimde bir konteyner yoksa komutu hatalı sonlandırma diye veya operatörüyle || true çık. Böylece devamında && ile birleştirilmiş ikinci komutu da çalıştır.

$ docker rm -f $(docker ps -a | awk '/ cem$/ {print $1}') || true

Konteyner Yaratırken WORKDIR Belirlemek

Port ve Disk bağlama malum. WORKDIR kısmına biraz bakalım.

WORKDIR komutu, herhangi bir zamanda bir Docker konteynerin çalışma dizinini tanımlamak için kullanılır. WORKDIR instruction (talimatı), Dockerfile dosyasında belirtilir. Herhangi bir RUN, CMD, ADD, COPY veya ENTRYPOINT komutu WORKDIR ile belirtilen çalışma dizininde yürütülür.

Aşağıdaki ekran görüntüsü şunu diyor:

  • Çalışan konteynerin terminaline kendi konsolumuzu bağlanmak istediğimiz zaman (exec -i -t --user="root" veya exec -it)
  • Doğrudan WORKDIR ile belirtilen dizine iniş yaparsın.
  • pwd İle hangi klasördeyim diye çalıştırdığında konsola yazdırılan dizin, Dockerfile içinde verilen WORKDIR ile aynıdır.
WORKDIR talimatı (instruction)

Bizim docker cli komutumuza bir daha bakalım ve -w anahtarıyla hangi dizini WORKDIR diye ayarladığımızı görelim:

docker run --name=cem          \
-p 81:3000 \
-v $(pwd):/var/www \
-w /var/www \
node:12 \
ls -l
  • WORKDIR talimatını Dockerfile içinde n kere farklı dizinleri işaret edecek şekilde kullanabilirsiniz.

Mesela web sayfalarını tutacağınız klasörü www dizini olarak WORKDIR ile tanımladınız (WORKDIR /home/www) ve hemen peşine npm paketlerini yüklemek için install komutunu çalıştırdınız

WORKDIR /home/www
RUN npm install

Sonra gidip /home/log dizini yaratma işini kolayca WORKDIR ile yapıp, dizinin içine log dosyasını kopyalamak için COPY komutunu çalıştırdınız

WORKDIR /home/logs
COPY full.log .

Sonra dilediğiniz kadar WORKDIR talimatını (instruction) kullanarak ADD, ENTRYPOINT vs. gibi talimatları peşi sıra çalıştırabilirsiniz.

Ref: https://www.educative.io/edpresso/what-is-the-workdir-command-in-docker

Konteynere İlk Argümanları Geçirmek

Yansı çalıştırıldığında

Inspect ettiğimizde göreceğimiz bazı property bilgilerinde görürüz ki:

"Args": [
"ls",
"-l"
],
...
"Config": {
...
"Cmd": [
"ls",
"-l"
],

docker inspect cem İle Konteynerin bizi ilgilendiren kısımlarını inceleyelim

$ docker inspect cem
[
{
...
"Args": [
"ls",
"-l"
],
...
"Name": "/cem",
...
"HostConfig": {
"Binds": [
"/c/Users/cem.topkaya/docker/nodeApp:/var/www"
],
...
"PortBindings": {
"3000/tcp": [
{
"HostIp": "",
"HostPort": "81"
}
]
},
,...
},
...
"Mounts": [
{
"Type": "bind",
"Source": "/c/Users/cem.topkaya/docker/nodeApp",
"Destination": "/var/www",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
"Config": {
...
"ExposedPorts": {
"3000/tcp": {}
},
...
...
"Cmd": [
"ls",
"-l"
],
"Image": "node:12",
"Volumes": null,
"WorkingDir": "/var/www",
...
...
},
...
}
}
]

Birden fazla PORT ve VOLUME bağlamak

Image’den bağımsız olarak istediğimiz kadar port ve volume mapping yapabiliyoruz. Yansının içinde kullanılmayabilir elbette!

$ docker inspect cem
[
{
...
"Args": [
"ls",
"-l",
"users"
],
...
"Name": "/cem",
...
"HostConfig": {
"Binds": [
"/c/Users/cem.topkaya/docker/nodeApp:/var/www",
"/c/Users/cem.topkaya:/var/www/users"
],
...
"PortBindings": {
"3000/tcp": [
{
"HostIp": "",
"HostPort": "81"
}
],
"3002/tcp": [
{
"HostIp": "",
"HostPort": "82"
}
]
},
...
},
...
"Mounts": [
{
"Type": "bind",
"Source": "/c/Users/cem.topkaya/docker/nodeApp",
"Destination": "/var/www",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "bind",
"Source": "/c/Users/cem.topkaya",
"Destination": "/var/www/users",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
"Config": {
...
"ExposedPorts": {
"3000/tcp": {},
"3002/tcp": {}
},
...
"Cmd": [
"ls",
"-l",
"users"
],
"Image": "node:12",
"Volumes": null,
"WorkingDir": "/var/www",
...
"OnBuild": null,
"Labels": {}
},
...
}
}
]

Örnek Dockerfile

Bir Yansıyı Yeninden Adlandırmak

Bir yansının adını değiştirmek için tag anahtarı kullanılır. Bu işlem aynı yansının yeni isimle kopyalanmasını sağlar. Eğer tek bir yansı ve yeni adıyla kalmasını istiyorsak en son olarak eski isimdeki yansının kaldırılması ortalığı temiz bırakmak için iyidir.

Bu iki komutla işimiz tamam oluyor:

$ docker image tag hello-world merhaba-dunya
$ docker rmi hello-world

Detaylı konsol hareketleri:

cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker image ls --filter "reference=hello-world"
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest fce289e99eb9 15 months ago 1.84kB
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker image tag hello-world merhaba-dunya
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker image ls -a | awk '/fce289e99eb9/'
hello-world latest fce289e99eb9 15 months ago 1.84kB
merhaba-dunya latest fce289e99eb9 15 months ago 1.84kB
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker rmi hello-world
Untagged: hello-world:latest
Untagged: hello-world@sha256:9572f7cdcee8591948c2963463447a53466950b3fc15a247fcad1917ca215a2f
Untagged: hello-world@sha256:f9dfddf63636d84ef479d645ab5885156ae030f611a56f3a7ac7f2fdd86d7e4e
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker image ls -a | awk '/fce289e99eb9/'
merhaba-dunya latest fce289e99eb9 15 months ago 1.84kB

Örnek

Etiketler: grep, awk, run, ls, inspect, attach, start, exec

Grep kullanırken tablo olarak dönen verinin sütunları arasında $sayı ile (satırda boşluk karakteriyle ayrılan her alan bir sayı olacak) ayrıştırıyoruz.

grep Kullanımı

grep satırdaArananKelime ile satır içinde aranan kelime varsa bu satırı süzülecek sonuç satırları arasına ekliyor.

grep -m1 -A1 Exposed ise bir metin dosyasını cat ile konsolda gösterir gibi çalışırken bu kez sütun temelli değil, satır temelli çalışır. Eşleşen (mathced) ilk satırı (1) almak için -m1 ve bu satırla birlikte devam eden (-A) ilk satırı (1) almak için -A1 anahtarını kullanıyoruz.

m: Eşleşen sonuç miktarını kısıtlamak için kullanılır. Bu örnekte 1 eşleşmeden sonrasına bakılmasın istersek -m1 anahtarını kullanırız

Context control:
-B, --before-context=NUM print NUM lines of leading context
-A, --after-context=NUM print NUM lines of trailing context
-C, --context=NUM print NUM lines of output context
-NUM same as --context=NUM
--color[=WHEN],
--colour[=WHEN] use markers to distinguish the matching string
WHEN may be `always', `never' or `auto'.
-U, --binary do not strip CR characters at EOL (MSDOS)
-u, --unix-byte-offsets report offsets as if CRs were not there (MSDOS)

A : Eşleşmeyi sağlayan satırdan sonraki n satırı almak için (yukarıda 1 satır için -A1 kullanılmış):

Exposed kelimesi iki satırda geçiyor — işaretiyle bu iki eşleşme ayrık gösteriliyor.

a: Eşleşmeyi sağlayan satırın önceki ve sonraki n satırı için :

-a1 : Eşleşen satırın 1 üst ve 1 alt satırlarını getirir

B: Eşleşmeyi sağlayan satırın n satır öncesini getirir (burada -B2 anahtarı kullanılmış ve eşleşen satırla birlikte öncesindeki 2 satırı da getirir):

-B2 : Eşleştiği satırla birlikte önceki 2 satır

b: Satır numaralarıyla birlikte önceki n satırı döner:

C : Eşleşen satırın önündeki ve ardındaki n satırı getirir:

-C1 ile eşleşen satırı önceki ve sonraki 1 satırla döner

Not: Dilerseniz grep yerine awk kullanımı için bu adrese bakabilirsiniz

AWK İle eşleşen ve sonraki satırı getirmek için (buraya bakabilirsiniz):

1. print eşleşen satırı yazar, getline ile sonraki satırı alır; 2. print ile bu satırı yazarız
  1. Yansı yüklü mü?
$ docker image ls -a --filter "reference=cemt-node"

2. Yansı hiç konteyner olarak çalıştırılmış mı? cemt-node Yansısı veya bu yansıdan üretilmiş herhangi bir kapsayıcı var mı diye süzmek için ancestor anahtarını kullabiliriz.

$ docker container ls -a --filter "ancestor=cemt-node" --format "table {{.ID}} \t {{.Image}}"

3. Yansının içerisinde hangi portlar dışarıdan kullanılacak şekilde expose edilmiş?

$ docker inspect cemt-node | grep -m1 -A1 Exposed
awk ile Expose edilen portları getirelim

4. Yansıyı,

  • (run) çalıştıralım
  • (-d) bağlantısız (detached) başlasın ki; işlemi yaptığımız terminal/konsolumuz (Command Line) yansının konsol çıktılarıyla bloklanmasın
  • (-p 88:91) yansının 91 portunda çalışan servisine dışarıdan 88 portuyla bağlanabilmek için port mapping yapalım
  • (--name cemt-node) yansıdan oluşturulacak konteynerin adını cemt-node diye tayin edelim
$ docker run 
-d
-p 88:91
--name cemt-node-container
cemt-node

5. ("ancestor=cemt-node" ) Anahtarıyla bakalım cemt-node adlı yansımızdan bir konteyner oluşmuş mu diye tüm konteynerleri (-a )süzelim (--filter ) ve sonuçlardan sadece ID ve IMAGE bilgilerini tablo şeklinde görüntülemek için çıktıyı formatlayalım (--format "table {{.ID}} \t {{.Image}}" )

$ docker container ls 
-a
--filter "ancestor=cemt-node"
--format
"table {{.ID}} \t {{.Image}}"

6. Elbette konteyner bağlantısız (detached) modda çalıştığı için (f08e88... diye bir ID ile)

  • çalıştığında ekranda ne yazdı,
  • şu anda konsola bir log yazıyor mu

bilemiyoruz. Bari şu anda yazdıklarını görmek için konsolumuzu çalışan konteynerimize bağlayalım (attach):

$ docker attach f0

7. Aynı işi konteyner başlatılırken -a anahtarıyla da yapabiliriz ama hep bağlantı kurulduktan sonraki konsol hareketlerini görebiliriz. Sadece -a anahtarı bize stdOut ve stdErr çıktılarını görmemizi sağlar. Eğer müdahale etmek istersek -i (interactive) anahtarıyla bağlanıp stdIn ile girdilerimizin konteynere akmasını sağlayabiliriz.

$ docker start -a f08

8. Oysa konteyner başlatıldığı andan itibaren eski kayıtları da görmek istersek logs anahtarını kullanmamız gerekir

$ docker container logs f0

Ve tüm hareket

cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker image ls -a --filter "reference=cemt-node"
REPOSITORY TAG IMAGE ID CREATED SIZE
cemt-node latest 1394c4bd5c2f 7 minutes ago 921MB
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker container ls -a --filter "ancestor=cemt-node" --format "table {{.ID}} \t {{.Image}}"
CONTAINER ID IMAGE
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker inspect cemt-node | grep -m1 -A1 Exposed
"ExposedPorts": {
"91/tcp": {}
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker run -d -p 88:91 --name cemt-node-container cemt-node
f08e8898319d9d602ca7f3ba8be084a3435205deb77c197f5ea944ecbb106f8c
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker container ls -a --filter "ancestor=cemt-node" --format "table {{.ID}} \t {{.Image}}"
CONTAINER ID IMAGE
f08e8898319d cemt-node
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker container attach f0
talep düştü ------
GET /
talep düştü ------
GET /
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker start -a f0
talep düştü ------
GET /
talep düştü ------
GET /
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$ docker container logs f0
Sunucu Baslatildi:0.0.0.0:91
talep düştü ------
GET /
talep düştü ------
GET /
talep düştü ------
GET /
talep düştü ------
GET /
talep düştü ------
GET /
cem.topkaya@CEM-TOPKAYA-PC MINGW64 /c/Temp/docker/nodeApp
$

Bir başka örnek:

Sürekli olarak çalışan işlemleri stdOut olarak yazar# docker run --name -d istemci ubuntu /usr/bin/top bEğer attach ile bağlanırsak o an stdOut'a düşenleri görürüz
# docker attach istemci
Eğer logs ile bağlanırsak en başından beri yazdıklarını görürüz
# docker logs istemci

-- init

-- init İle İşlemi Önceliklendirmek

Jenkins master düğümüne bir slave yarat ama bu bir docker slave olsun demek istedim ve init anahtarıyla karşılaştım. Ve öğrenmek için yola koyuldum.

Öncelikle slave düğümü yaratması için master node’a, docker slave node olması için “şu komutu çalıştırarak slave düğümü ateşle” diyoruz:

docker run -i --rm --name agent --init jenkins/agent java -jar /usr/share/jenkins/agent.jar
docker run -i — rm — name agent — init jenkins/agent java -jar /usr/share/jenkins/agent.jar

Ne anlama geldiğine bakalım:

-i     : Bağlı olmasa bile STDIN'i açık tut. Yani konsolunu konsoluma bağlamasam bile konsoldan giriş yapabilecek gibi başlat--rm   : Konteynerden çıkış yapılırsa listeden sil--init : konteyner başlatıldığında çalıştırılsın diye girdiğimiz komutu PID 1 olacak şekilde başlat. Yani init process olarak yani ilk başlatılacak program agent.jar olsun ki birinci process agent.jar olabilsin. Böylece Process ID değeri 1 olur.
Nedir o komut: java -jar /usr/share/jenkins/agent.jar
docker exec -it 2df /bin/bash && ps aux

Docker CLI’dan bir Docker Container için stop komutu verildiğinde Docker Engine, ilgili Container’ın init Process’ine SIGTERM sinyali göndermekte 10 saniye (ayarlanabilir bir değer) beklemekte ve sonrasında eğer Container kendi isteği ile çıkış yapmazsa yine init Process’ine bu kez SIGKILL sinyalini göndermekte ve Container’ı sonlandırmaktadır. Docker Daemon, init Process’e SIGTERM gönderdikten sonra 10 saniye bekleyerek Container’ın (varsa) halihazırda yaptığı bir işi sonlandırmasını beklemektedir. Bu davranış geleneksel Linux/Unix sistem mimarisi ile uyumlu bir davranıştır. Linux/Unix’te bir Process veya tüm sistem kapatılmak istendiğinde Process’lere SIGTERM sinyali gönderilir ve 5-10 saniye boyunca kendilerinin çıkış yapması beklenir, Process’ler çıkış yapmazsa SIGKILL veya sistem kapatılması suretiyle sonlandırılırlar.

Nginx’i bir Bash Script ile başlattığımızda SIGTERM sinyali, init Process’i olan bash Process’ine gönderilmekte ve sinyal Nginx’e aktarılmadığı için Nginx çalışmaya devam etmektedir.
Ref: Gökhan Şengün

Docker Alıntıları

When you deploy a new Docker container, you specify the name and tag of the image that you want to use for the container. The Docker host checks if that image is locally available. If it is not available, the Docker host downloads all of the layers that are needed for that particular image. If layers are shared between images, they are never downloaded twice. For example, you want to run a container with WordPress and another container with MySQL. Both containers are based on a base Ubuntu image, and the layers that make up that Ubuntu image are only downloaded once. This split into layers makes Docker images more easily available, because a Docker image is not one large, monolithic file.
Ref: Inside a Docker image

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:

Hatadan öğrenebileceklerimiz:

Tcp ip terminolojisinde, uygulama seviyesinde (application level) bir bağlantıyı simgeleyen sanal yapıya socket denir. Unixde bu bağlantı bir dosyadır (file descriptor). Bu yüzden /var/run/docker.sock dosyası bir soket bağlantısının nereye yapılacağını içerir.

Unix ve File descriptors üzerine güzel bir sunum izlemek isterseniz:

Docker işlemleri arka tarafta bir API üstünden gerçekleşir ve bu API’ye http isteği yapılır. Nitekim aşağıdaki versiyon bilgisinin çekilmesi için http://%2Fvar%2Frun%2Fdocker.sock/v1.40/version adresine (http:///var/run/docker.sock/v1.40/version) bir istek yapılıyor.

Bu ve aşağıdaki tüm sorunların temelinde unix:///var/run/docker.sock dosyasına erişmek için docker çalıştırdığımız makinedeki kullanıcımızın yeterli izni yok.

Docker istemcimizin versiyon bilgisini docker.exe veriyor ancak sunucu tarafındaki versiyon bilgisini çekmek istediğimizde -ki bunu da REST API çağırısıyla yapıyoruz- “Permission denied” hatası alıyoruz.

$ docker version
Client:
Version: 19.03.6
API version: 1.40
Go version: go1.12.17
Git commit: 369ce74a3c
Built: Wed Oct 14 19:00:27 2020
OS/Arch: linux/amd64
Experimental: false
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/version: dial unix /var/run/docker.sock: connect: permission denied

Docker istemicimiz ile docker sunucu üstünde bir yansı oluşturmak istediğimizde bu kez farklı bir REST API isteği yapmak için docker sunucu bilgisini tutan /var/run/docker.sock dosyasına erişerek unix socket yapmak istiyor ama yine erişim izini olmadığı için hata alıyor: http:///var/run/docker.sock/v1.40/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&session=x5fz7fe61xll8c73jmzg38grn&shmsize=0&t=yeni&target=&ulimits=null&version=1

$ ll
total 8
drwxrwxr-x 2 cemt cemt 4096 Eki 26 09:11 ./
drwxr-xr-x 18 cemt cemt 4096 Eki 26 09:11 ../
-rw-rw-r-- 1 cemt cemt 0 Eki 26 09:11 Dockerfile
$ docker build -t yeni .
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.40/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&session=x5fz7fe61xll8c73jmzg38grn&shmsize=0&t=yeni&target=&ulimits=null&version=1: dial unix /var/run/docker.sock: connect: permission denied

1. Çözüm

Permission denied ile yeterli izin olmadığı için sudo ile çalıştırmamız gerektiğini düşünebiliriz. Voila! Bu çözümlerden biridir:

$ sudo docker version
Client:
Version: 19.03.6
API version: 1.40
Go version: go1.12.17
Git commit: 369ce74a3c
Built: Wed Oct 14 19:00:27 2020
OS/Arch: linux/amd64
Experimental: false
Server:
Engine:
Version: 19.03.6
API version: 1.40 (minimum version 1.12)
Go version: go1.12.17
Git commit: 369ce74a3c
Built: Wed Oct 14 16:52:50 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.3.3-0ubuntu1~18.04.2
GitCommit:
runc:
Version: spec: 1.0.1-dev
GitCommit:
docker-init:
Version: 0.18.0
GitCommit:

Aynı şekilde çalışan docker kaplarını görmek istediğimizde yine “permission denied” hatası alacağız:

$ docker ps -a
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/json?all=1: dial unix /var/run/docker.sock: connect: permission denied
sudo ile çalıştırınca sorun çözülüyor

docker.sock Dosyasını okumak için de sudo işe yarayacaktır:

Kalıcı çözüm ise aktif kullanıcıyı (echo $USER komutuyla görebilirsiniz) docker kullanıcı grubuna eklemek olackatır. cemt Kullanıcısın dahil olduğu grupları görelim:

/etc/group

cat /etc/group
$ echo $USER
cemt
$ id
uid=1000(cemt) gid=1000(cemt) groups=1000(cemt),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),122(sambashare)
$ cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog,cemt
...
cdrom:x:24:cemt
...
sudo:x:27:cemt
dip:x:30:cemt
...
cemt:x:1000:
sambashare:x:122:cemt
docker:x:123:
$ getent group $USER

docker grubunda cemt kullanıcısının olmadığına dikkat edelim!

/etc/passwd

Şimdi /etc/passwd dosyasına bakalım:

cat /etc/passwd
s$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
(admin):/var/lib/gnats:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
cemt:x:1000:1000:cemt,,,:/home/cemt:/bin/bash
dnsmasq:x:112:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin

/etc/passwd dosyasında tüm kullanıcıların bilgilerini (şifreleri x olarak görürüz) bulabiliriz.

cemt    :x    :1000        :1000   :cemt,,,  :/home/cemt :/bin/bash
username:şifre:Kullanıcı ID:grup ID:açıklama :ev dizini :default sh

/nologin varsayılan bash uygulaması bize şunu anlatır: Bu kullanıcı sisteme giriş yapmaz ve bu yüzden varsayılan bir kabuk çalışmaz.

/nonexistent Bu kullanıcının varsayılan bir ev dizini yoktur demek.

/etc/passwd dosyasında okuduğumuz bilgileri /etc/group dosyasındakilerle birleşmiş olarak görmek için $ id komutunu çalıştırabiliriz.

$ id
uid=1000(cemt) gid=1000(cemt) groups=1000(cemt),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),122(sambashare)

Aktif kullanıcı içinde id komutu group ve passwd dosyalarında kullanıcı için tanımlı satırların toplamıdır:

$ id = /etc/passwd + /etc/group

cemt Kullanıcısının dahil olduğu grupları $ groups cemt komutuyla görebiliriz:

groups <kullanıcı adı>

2. Çözüm

Ve şimdide kalıcı çözüm olarak docker komutlarını çalıştıracağımız kullanıcıyı (örneğimizde cemt) docker grubuna ekleyelim:

$ sudo usermod -aG docker $USER

Sonuç olarak artık docker grubunda cemt kullanıcısını görmemiz gerekiyor:

groups cemt
$ id
uid=1000(cemt) gid=1000(cemt) groups=1000(cemt),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),122(sambashare),123(docker)

/etc/group Dosyasından baktığımızda:

$ cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog,cemt
...
cdrom:x:24:cemt
...
sudo:x:27:cemt
.
dip:x:30:cemt
...
plugdev:x:46:cemt
...
ssh:x:115:
lpadmin:x:116:cemt
...
cemt:x:1000:
sambashare:x:122:cemt
docker:x:123:cemt

Bilgisayarı tekrar başlattığımızda (belki tekrar login olmak da çözebilirdi) artık sudo olmadan da /var/run/docker.sock dosyasına erişmek isteyen docker komutlarının çalıştığını görebileceğiz:

docker version

Docker Servisi Nasıl Çalışıyor?

İlgileneceğimiz kısım docker servisi çalıştıktan sonra durum bilgisine bakarak “hangi dosyalar kullanılmış” sorusuna cevap bulmak.

Docker yüklendiğinde

  • /usr/bin/dockerd çalıştırılabilir dosyası
  • /lib/systemd/system/docker.service servis başlatılabilsin diye
  • /var/run/docker.sock üstünden unix socket yapılsın diye
docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; disabled; vendor preset: enabled)
Active: active (running) since Mon 2020-10-2....; 1min 20s ago
Docs: https://docs.docker.com
Main PID: 6659 (dockerd)
Tasks: 10
CGroup: /system.slice/docker.service
└─6659 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
...
Your kernel does not support cgroup rt runtime
Your kernel does not support cgroup blkio weight
Your kernel does not support cgroup blkio weight_device
Loading containers: start.
Default bridge (docker0) is assigned with an IP address 172.17.0.0/16.
Daemon option --bip can be used to set a preferred IP add
Loading containers: done.
"Docker daemon" ... version=19.03.6
Daemon has completed initialization
API listen on /var/run/docker.sock

/lib/systemd/system/docker.service Dosyasında dockerd uygulamasına anahtar olarak -H verildiğine dikkat çekeyim. Docker paketi kurulduğunda servisi /lib/systemd/system dizini içinde docker.service dosyasıyla kuruluydu.

Bir servisin (systemd) olabileceği dizinler şunlardır:

  • /etc/systemd/system Yerel konfigürasyon
  • /run/systemd/system Çalışma zamanı birimleri (runtime units)
  • /usr/lib/systemd/system ||/lib/systemd/systemKurulu paket birimleri

Okunmalı:

/lib/systemd/system/docker.service
-H Daemon socket(s) to connect to

Docker daemon’un (docker istemcimizin) işlemlerimizi hangi docker sunucu üstünde gerçekleştirmesini istiyorsak -H parametresine o docker sunucusunun IP adresini vereceğiz. Varsayılan olarak docker işlemleri yerel makinemizde (localhost) gerçekleşecektir ve 0.0.0.0 adresiyle tüm ağ arayüzlerinden erişebileceğiz.

Elle /usr/bin/dockerd uygulamasını çalıştırıp parametrelerini kendimiz vermek istediğimizde /var/run/dockerd.pid dosyasının var olduğunu yani servisin çalıştığını, eğer servisi durdurursak dockerd uygulamasını çalıştırabileceğimizi söyleyecek:

Servise bağlı tüm dosyalardan farklı dosyaları gösterirsek (/var/run/docker.pid, /var/run/docker.sock …) çalıştırabiliriz. Ancak biz dockerd hizmetini durdurarak elle çalıştırmayı ve kendi parametrelerimizi vermeyi deneyelim.

Sanal makinemizdeki docker daemon hizmetini durdurup, windows makinemizde “busybox” kalıbını windows içinde kurmak isteyelim:

docker istemcimize diyoruz ki; komutları çalıştıracağın sunucu adresi sanal Lubuntu işletim sistemli makinemiz olan 192.168.30.130 olsun. docker run busybox komutunu çalıştır. Ancak Lubuntu sanal makinesinde 2375 portunda docker daemon çalışmadığı için “actively refused it” mesajını görüyoruz.

Şimdi docker daemon uygulamasını her ağ arayüzünden 2375 portundan gelecek istekleri çalıştıracak şekilde elle başlatalım:

Sanal makinede yansı ve kap olmadığını gördük. Şimdi windows host makinemizde DOCKER_HOST ayarını tcp://192.168.30.130:2375 olarak ayarlayalım ve tüm docker komutlarını sanal makinemiz üstünde çalıştıralım:

Sol taraftaki iki pencere sunucumuz olan Lubuntu sanal makinemizde çalışıyorken sağ taraftaki pencere host işletim sistemi olan windows makinesinde çalışıyor. Lubuntu üstündeki docker versiyonunun, windows host penceresinde Server kısmıyla aynı olduğuna dikkat!

docker info

docker events

Docker sunucudaki olayları yakalamak için docker events komutunu kullanabiliriz *:

Şimdi sunucumuzda elle çalıştırmak yerine servis yaratarak otomatik çalıştıralım ama önce bir *.service dosyasının yapısını inceleyelim (kaynak):

[Unit]
Description=Hizmetle ilgili açıklama buraya yazılır
# unit’imizin hangi servislerden sonra başlatılacağını belirtiyoruz
# servisimiz docker ile çalıştığı için ön şart olarak docker olacak
After=docker.service
# After ile sadece belirtilen servisin başlayıp başlamadığı kontrol edilir.
# Eğer After ile belirtilen servis başlamışsa bizim servisimizde başlatılacaktır.
# Requires ve Wants seçenekleriyle belirttiğimiz servisleri otomatik olarak tetikleyecektir.
# Farkı ise :
# - “Requires”da belirttiğimiz servis başlamaz ise bizim servisimizde başlamayacaktır
# - “Wants”da soft dependency vardır yani eğer belirttiğimiz servis başarısız olsa bile bizim servisimiz tetiklenecektir.
# Buna göre docker çalışmazsa bizim servisimizin de çalışmasının anlamı olmayacağı için
Requires=docker.service
# Service kısmında, unit başladığı anda hangi komutların çalıştırılacağına dair bilgileri koyarız.
[Service]
Restart=always
# ExecStart ile servisimiz başladığı anda hangi komutun çalıştıracağını belirtiriz
# ExecStartPre veya ExecStartPost seçeneklerini de koyabiliriz,
# ExecStop ile servis durdurulduğu anda hangi komutun çalıştırılacağı,
# ExecReload ile servis yenilendiğinde hangi komutun çalıştırılacağını belirtiriz
ExecStart=/usr/bin/docker start -a neo4jdocker
ExecStop=/usr/bin/docker stop -t 3 neo4jdocker
[Install]
# Hedefleri listele: $ systemctl list-units --type target
# Varsayılan hedefi: $ systemctl get-default
# Tüm servisler: $ systemctl list-units --type=service --all
# Servis ayrıntısı: systemctl cat application.service
# Servis ayrıntısı: systemctl show application.service
#
WantedBy=local.target
systemctl list-units --type target

Şimdiz hizmetimizi yapılandıralım:

Varolan servis dosyasını kontrol etmek için komutunu çalıştırıp hizmetin durumundan yerini tespit ederiz (zaten yukarıda görmüştük).

systemctl list-units --type=service --all | grep docker

Servisin yerini görmek için durumuna bakalım:

service docker status

Docker’ın dinleyeceği ağ arayüzlerini 0.0.0.0 olarak verip dışarıdaki docker istemcilerinin erişebilmesini sağlarız:

Eğer yeni bir hizmet oluşturmak istersek:

# Aşağıdaki komutların docker host makinesinde (linux olacak tabi) 
# çalışması gerekiyor
# root yetkimiz olduğu için /etc/systemd/system/ dizininde yeni bir dizin oluşturalım:
sudo mkdir -p /etc/systemd/system/docker.service.d
# Hizmet seçeneklerini yüklemek için aşağıdaki dosyayı oluşturalım
sudo vi /etc/systemd/system/docker.service.d/options.conf
# Yukarıdaki tanımdan daha özet bir hizmet olacak:
[Service]
ExecStart=/usr/bin/dockerd -H unix:// tcp://0.0.0.0:2375
# systemd tekrar yüklensin ve bizim hizmetimizin bağlantısı oluşsun
sudo systemctl daemon-reload
# Artık docker 0.0.0.0:2375 soketini dinlesin diye yeniden başlasın:
sudo systemctl restart docker
# veya
sudo service docker restart
# durdurmak için
sudo systemctl stop docker
# veya
sudo service docker stop
# durumunu öğrenmek için
sudo systemctl status docker
# veya
sudo service docker status

Çok Satırlı RUN Komutuyla *.service Dosyası

Çok satırlı RUN komutunun içinde ARG kullanmak için bu adres benim yardımcım oldu.

docker build --build-arg NF_PAKET_ADI=cnrnrf -t jcinardocker:latest .
FROM cinardocker:2
ARG NF_PAKET_ADI
RUN echo '[Unit]\n\
Description=Paketleri kuracak olan servistir. \n\
After=network.target\n\n\
[Service]\n\
Type=oneshot\n\
ExecStart=/usr/bin/apt install -y '$NF_PAKET_ADI'\n\n\
[Install]\n\
WantedBy=multi-user.target' > /etc/systemd/system/IlkAcilistaPaketKur.service
RUN systemctl enable IlkAcilistaPaketKurENTRYPOINT [ "/sbin/init" ]

Dockerfile üstünden yansıyı inşa etmek için argumanları önce tag sonra ve en son Dockerfile dosyasının yerini gösteriyoruz:

docker build                           \
--build-arg NF_PAKET_ADI=cnrnrf \
-t jcinardocker:latest \
.
veya docker build \
--build-arg NF_PAKET_ADI=cnrnrf \
-t jcinardocker:latest \
-f ./Dockerfile

Docker içinde servis yükleyen paketler için dikkat edilmesi gerekenler

$ docker build -t nf:1 --no-cache .
Sending build context to Docker daemon 242.1MB
Step 1/7 : FROM cinar/docker
---> 102992f7341e
Step 2/7 : ENV container docker
---> Running in 4bbf37613c41
Removing intermediate container 4bbf37613c41
---> 8ab4839c60f1
Step 3/7 : ENV container docker
---> Running in 7fc14cde37bc
Removing intermediate container 7fc14cde37bc
---> e480943de88b
Step 4/7 : ARG NF_PAKET_ADI=cnrnssf
---> Running in 4b844225e559
Removing intermediate container 4b844225e559
---> a486ab591fe5
Step 5/7 : RUN echo $(env)
---> Running in a2637f24b07f
HOSTNAME=a2637f24b07f HOME=/root PACKAGE_NAME=CinarDocker container=docker NF_PAKET_ADI=cnrnssf PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin DEBIAN_FRONTEND=noninteractive LC_ALL=C PWD=/root
Removing intermediate container a2637f24b07f
---> f1b8c0bb528b
Step 6/7 : COPY ./cnrnssf_0.7.21.405_amd64.deb /root/
---> 810d3fb4020f
Step 7/7 : RUN dpkg -i /root/cnrnssf_0.7.21.405_amd64.deb
---> Running in df437930a919
Selecting previously unselected package cnrnssf.
(Reading database ... 27919 files and directories currently installed.)
Preparing to unpack .../cnrnssf_0.7.21.405_amd64.deb ...
Unpacking cnrnssf (0.7.21.405) ...
Setting up cnrnssf (0.7.21.405) ...
ContainerrrR: docker
Failed to connect to bus: No such file or directory
Created symlink /etc/systemd/system/default.target.wants/cnrnssf.service, pointing to /lib/systemd/system/cnrnssf.service.

Removing intermediate container df437930a919
---> 11ec595681da
Successfully built 11ec595681da
Successfully tagged nf:1

Kurmak için kullandığımız RUN komutu ve içeriğindeki postinst kabuk betiğinin çıktılarını inceleyelim.

ContainerrrR: docker yazısını gördüğümüzde postinst betiğinin içine girdiğimizi anlıyoruz. postinst Kabuk betiğindeki “systemctl daemon-reload” komut çalıştığında “Failed to connect to bus: No such file or directory” hatası fırlatılıyor. Daha sonra cnrnssf.service dosyası enable edildiğinde (systemctl enable cnrnssf) symlink yaratıldı mesajını görüyoruz.

Step 6/6 : RUN dpkg -i /root/cnrnssf_0.7.21.405_amd64.deb
---> Running in 1cab9852e9e2
Selecting previously unselected package cnrnssf.
(Reading database ... 27919 files and directories currently installed.)
Preparing to unpack .../cnrnssf_0.7.21.405_amd64.deb ...
Unpacking cnrnssf (0.7.21.405) ...
Setting up cnrnssf (0.7.21.405) ...
ContainerrrR: docker
Failed to connect to bus: No such file or directory
Created symlink /etc/systemd/system/default.target.wants/cnrnssf.service, pointing to /lib/systemd/system/cnrnssf.service.
Removing intermediate container 1cab9852e9e2

Kabuk betiği:

echo "ContainerrrR: "$containersystemctl daemon-reload
systemctl enable cnrnssf
if [ ! -n "$container" ]; then
echo "start edecegummm"
systemctl start cnrnssf || true
fi

Bu kez system daemon-reload komutunu eğer docker için build ediyorsak çalıştırmayacağız diye if bloğuna kaydırırsak aşağıdaki gibi, hata almadığımızı göreceksiniz:

$ sudo dpkg-deb -b packages/edit/ cnrnssf_0.7.21.405_amd64.deb
dpkg-deb: building package ‘cnrnssf’ in ‘cnrnssf_0.7.21.405_amd64.deb’.
— — — — — — — — — — — — — — — — — — — — — — — -
$ cat packages/edit/DEBIAN/postinst
#!/bin/sh
id -u cnrusr > /dev/null 2>&1
if [ “$?” = “1” ]; then
useradd cnrusr
fi
chown -R cnrusr:cnrusr /var/log/cinar/nssf
chown -R cnrusr:cnrusr /etc/cinar/nssf
chown -R cnrusr:cnrusr /opt/cinar/nssf
echo “ContainerrrR: “$container
systemctl enable cnrnssf
if [ ! -n “$container” ]; then
echo “start edecegummm”
systemctl daemon-reload
systemctl start cnrnssf || true
fi
— — — — — — — — — — — — — — — — — — — — — — — -
$ cat Dockerfile
FROM cinar/docker
ENV container docker
ARG NF_PAKET_ADI=cnrnssfRUN echo $(env)COPY ./cnrnssf_0.7.21.405_amd64.deb /root/
RUN dpkg -i /root/cnrnssf_0.7.21.405_amd64.deb
— — — — — — — — — — — — — — — — — — — — — — — -
$ docker build -t nf:1 — no-cache .
Sending build context to Docker daemon 242.1MB
Step 1/6 : FROM cinar/docker
— -> 102992f7341e
Step 2/6 : ENV container docker
— -> Running in 47c94057a46b
Removing intermediate container 47c94057a46b
— -> fc8cf88b429c
Step 3/6 : ARG NF_PAKET_ADI=cnrnssf
— -> Running in 5c122cd15470
Removing intermediate container 5c122cd15470
— -> a05cd9070ca5
Step 4/6 : RUN echo $(env)
— -> Running in 3ba7e9646a9d
HOSTNAME=3ba7e9646a9d HOME=/root PACKAGE_NAME=CinarDocker container=docker NF_PAKET_ADI=cnrnssf PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin DEBIAN_FRONTEND=noninteractive LC_ALL=C PWD=/root
Removing intermediate container 3ba7e9646a9d
— -> 6ebed3579f41
Step 5/6 : COPY ./cnrnssf_0.7.21.405_amd64.deb /root/
— -> aadd7d22bfcc
Step 6/6 : RUN dpkg -i /root/cnrnssf_0.7.21.405_amd64.deb
— -> Running in debd61868314
Selecting previously unselected package cnrnssf.
(Reading database … 27919 files and directories currently installed.)
Preparing to unpack …/cnrnssf_0.7.21.405_amd64.deb …
Unpacking cnrnssf (0.7.21.405) …
Setting up cnrnssf (0.7.21.405) …
ContainerrrR: docker
Created symlink /etc/systemd/system/default.target.wants/cnrnssf.service, pointing to /lib/systemd/system/cnrnssf.service.
Removing intermediate container debd61868314
— -> 7687f7bc227e
Successfully built 7687f7bc227e
Successfully tagged nf:1

Servis kuran deb paketlerinin docker ile kapsayıcı olarak çalıştırılmasına dair yapılması gerekenleri sebepleriyle birlikte buraya not edeyim:

Amaç:

  • Web GUI’de her kütüphanenin bitbucket’a gönderilmesinde e2e testleri xTO ortamlarından bağımsız çalışarak uçtan uca (end-to-end) testleri için ilgili deb paketini back-end olarak ayaklandırmak için docker yansı/kapları kullanmak ve testin sonunda yok ederek kaynakları geri vermek.
  • deb Paketinin ayar dosyası parametre olara verilebiliyorsa, test senaryosuna uygun ayar dosyalarını persistent volume binding ile docker kabında çalıştırmak ve her test iterasyonu için farklı ayar dosyasıyla yeniden docker ayaklandırarak back-end olarak çalışan sunucu docker kabının ayarlarının geri alınması gibi test desenine (Given, When, Then) uygun olmayan kodlamadan kaçınılmasını sağlamak.
  • Test için farklı deb paketlerinden oluşan kapsayıcı kümelerinin ayaklandırılması gerektiğinde Docker-compose dosyalarıyla test ortamının hazırlanmasını kolaylıştırmak.
  • Paketlerinizin çalışacağı kütüphanelerin kurulu olduğu temel bir yansı hazırlanıp her deb paketinin sürümü için kalıp oluşturma işini dinamik hale getirmek ve deb paketlerinin sürümleri için önceden kalıplar oluşturmak yerine istenilen deb repodan paketinizi indirerek docker kalıbın, çalışma zamanı oluşturulmasını sağlamak .

Sorun:

deb paketlerimiz, docker yansıları oluşturmaya uygun yapılandırılmadıklarında apt install süreci hata ile kırılıyor. Çözüm olarak deb paketlerinin içeriğini dizinlere kopyalayarak ( apt install kullanılmadan) kalıp oluşturmak kalıyor. Ancak bu şekilde kurmak yerine apt install paket_adı komutunun işlerliğinin sağlanması gerekiyor.

Çözüm:

https://github.com/DataDog/datadog-agent/blob/master/omnibus/package-scripts/agent/postinst Adresindeki yapıya uygun veya alternatif yükleme scriptlerinin yazılması.

#!/bin/shsystemctl enable cnrnssfif [ ! -n "$container" ]; then
systemctl daemon-reload || true
systemctl start cnrnssf || true
fi

Ayrıntılı:

Temel Docker bilgisine ek olarak RUN, CMD ve ENTRYPOINT komutlarının farklarından başlayıp, hata alınan systemd’nin docker içinde çalışma şekline bağlayalım ve hatanın sebebini ortaya koyduktan sonra çözümü açıklayarak tamamlayalım.

Yansı hazırlanırken çeşitli güvenlik gerekçeleri nedeniyle FROM ile belirtilen kalıp, tam fonksiyonel olarak çalışmadığı için Dockerfile içinde kullanılan komutların farkları önem kazanıyor.

RUN

$ docker build . Komutu ile bir yansı/kalıp/image yaratırken, Dockerfile içindeki RUN satırlarını FROM ile belirtilen yansının içinde çalıştırmış oluruz.

CMD ve ENTRYPOINT

CMD ve ENTRYPOINT satırları nihayi ürün olan yansıdan yaratılacak kapsayıcı/kap/container içinde çalıştırılır.

CMD

Dockerfile içinde kap çalıştığında başlatılacak komut CMD ile belirtilirse:

FROM nginx:1.11-alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
# 1. bu yansıdan bir konteyner yarattığımızda, bir sınıfın örneğini oluşturur gibi imajın bir örneğini konteyner olarak örnekleriz
# 2. Dockerfile içinde 100 CMD olsa bile sonuncusu çalıştırılır
# 3. "daeomon on" deseydik arka planda çalışacak ve "docker run" tıpkı bir main metodunun sonuna gelip çıkması gibi bitecekti
CMD ["nginx", "-g", "daemon off;"]

Bu kalıptan bir kapsayıcı oluşturduğumuzda başlangıç komutunu şöyle ezebiliriz: $ docker run .... yansı_adı nginx -g daemon on

ENTRYPOINT

Tıpkı CMD gibi çalışır ancak ENTRYPOINT varsa CMD komutunda yazanlar, ENTRYPOINT’e parametre olarak eklenir. Yani çalıştırılan yine ENTRYPOINT olur. Bu yüzden systemd koşması için ENTRYPOINT [“/sbin/init”] ile systemd için PID 1 ayarı yapılmış olur.

$ apt install paketadı ile docker build aşamasında kurulum yapılırken systemd bağıntılı komutlar PID 1'in systemd olarak çalıştırılamaması nedeniyle başarıyla tamamlanamayacağı gibi yansı oluşturma da tamamlanamayacaktır. Bu yüzden FROM içinde kullanılan yansının systemd zincirinin kırılmadığı ve bir kalıp olması gerekmektedir. İkinci olarak docker build içinde özel yetkiler verilemeyeceği için (--privileged modu) PID 1 olarak servis çalışmıyor ve bir servisin başlatılması mümkün olmayacak. Bu sorunun dolaylı çözümü buildx eklentisi veya buildah kullanımı olacaktır. Ancak systemctl çağrılarında start argumanı gibi docker build aşamasında erişilemez çağrılardan uzak durularak da kurulum tamamlanacaktır. Nihayetinde hizmetin enable olarak işaretlenmesi ve kalıptan üretilecek kapsayıcının privileged modda çalıştırılması sayesinde uygulama hizmeti start komutuyla otomatik olarak başlatılacaktır.

--privileged

--privileged bayrağı olmaksızın docker kapsayıcı içinde systemctl ve service komutlarının çıktıları aşağıdaki gibi olup enable edilmiş hizmetlerin başlatılması için yetersizdir

root@87c5bed39b2f:/lib/systemd/system# systemctl
Failed to connect to bus: No such file or directory
root@87c5bed39b2f:/lib/systemd/system# service
Usage: service < option > | --status-all | [ service_name [ command | --full-restart ] ]
root@87c5bed39b2f:/lib/systemd/system# service cnrnssf status
cnrnssf: unrecognized service
root@87c5bed39b2f:/lib/systemd/system# service cnrnssf enable
cnrnssf: unrecognized service
root@87c5bed39b2f:/lib/systemd/system# systemctl enable cnrnssf
root@87c5bed39b2f:/lib/systemd/system# systemctl status cnrnssf
Failed to connect to bus: No such file or directory
root@87c5bed39b2f:/lib/systemd/system# systemctl start cnrnssf
Failed to connect to bus: No such file or directory

deb Paketlerinde systemctl start <servis_adı> ve systemctl daemon-reload komutlarını, docker içinde koşuyorsa çalıştırmamak gerekiyor. Bu nedenle postinst dosyası içinde container ortam değişkeni var ve docker ise bu iki komutu çalıştırmayacak şekilde işaretlemeli. Ayrıca komutların hata verme durumunda akışı kesmelerine karşı || true ile önlem alınabilir.

if [ ! -n "$container" ]; then
systemctl daemon-reload || true
systemctl start cnrnssf || true
fi

--privileged bayrağıyla kapsayıcı başlatmak

--privileged ile systemd harekete geçer ve hizmetler çalışır.

systemctl çalışıyor mu?

debconf: delaying package configuration, since apt-utils is not installed

--assume-yes ile -y aynı şeyi yani sorulara varsayılan cevaplarını vermek için kullanılır.

Bu hatayı çözmek için:

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --assume-yes apt-utils

DEBIAN_FRONTEND

One of debconf’s unique features is that the interface it presents to you is only one of many that can be swapped in at will. There are many debconf frontends available:

  1. dialog —Size soruları gösterir. Ssh tabanlı oturum üzerinden metin modunda çalışır.
  2. readline — Yavaş uzak bağlantılarla çalışırken soru cevap olarak Linux komut satırında cevapladığınız seçenektir.
  3. noninteractive — Apt aracılığıyla sistemi kurarken veya yükseltirken sıfır etkileşim gerektiğinde bu modu kullanırsınız. Tüm sorular için varsayılan cevabı kabul eder. Kök kullanıcıya bir hata mesajı gönderebilir, ama hepsi bu kadar. Aksi takdirde, tamamen sessiz ve alçakgönüllüdür, otomatik kurulumlar için mükemmeldir. Dockerfile, kabuk betikleri, cloud-init betikleri ve daha fazlasında böyle bir mod kullanılabilir.
  4. gnome —Bu, gtk ve gnome kitaplıklarını kullanan modern bir X GUI’dir.
  5. kde — Başka bir ön uç, Qt kitaplığıyla yazılmış basit bir X GUI sağlar. KDE masaüstüne çok iyi uyuyor.
  6. editor — Bu, her şeyi bir metin düzenleyicide yapmak zorunda olan fanatikler içindir. Düzenleyicinizi tipik bir unix yapılandırma dosyası gibi görünen bir dosya üzerinde çalıştırır ve bu dosyaları debconf ile iletişim kurmak için düzenlersiniz.
  7. web — sorulara göz atmak ve cevaplamak için web tarayıcınızla bağlandığınız bir web sunucusu görevi görür. Güvenlik nedeniyle bu seçeneği kullanmaktan kaçınılmalıdır.

Referans: https://www.cyberciti.biz

sürekli apt update ile yavaşlamamak için multi stage dockerfile

Sorun:

Benim örneğimde önce bir docker yansısı oluşturuyor, içinde apt update yapıyorum. Sonra bu yansıdan yeni bir kalıp oluşturup içinde tekrar apt update yapmak zorunda kalıyorum. Çünkü önceki kalıbın içinde /var/lib/apt/lists dizini içinde oluşturulan repo temelli paket listesi yeni kalıpta oluşmuyor. apt update ciddi bir yavaşlıkla çalıştığı için (/etc/apt/source.list içinde tanımlı repo adreslerini bulup hepsindeki paket listesini çekiyor) bunu hızlandırmak gerekti.

Çözüm:

# "apt update" komutuyla /etc/apt/source.list 
# dosyasında tanımlı repoların paket listesi çekilir ve
# /var/lib/apt/lists/ dizinine dosyalanır.
# Böylece apt install xxx_paketi dediğimizde uzak sunucuda
# sorgulanmaz. Tekrar tekrar "apt update" yapmamak için
# önceki yansıdaki repolara göre oluşturulmuş paket
# listelerini çekeceğiz
# Önceki yansıyı multi-stage yapısında bağlamak için:
FROM cinar/pre:latest AS pre
# pre İsimli yansıdan dosyaları yeni yansıya kopyalamak için:
FROM ubuntu
COPY --from=pre /var/lib/apt/lists/* /var/lib/apt/lists/

apt ile kurulumların non-interactive (sorgusuz)

# Aşağıdaki hata ve çözümü:
# debconf: delaying package configuration, since apt-utils is not installed
#
# apt-utils uygulamasını yükledikten sonra
# diğer apt yüklemelerinde -y komutuna gerek kalmıyor.
# apt-utils uygulaması DEBIAN_FRONTEND ortam değişkenlerine bakarak
# diğer yüklemelerin cevaplanmasını sağlıyor.
# DEBIAN_FRONTEND=noninteractive argumanı tanımlıyor
# apt-utils'i ister -y ister --asume-yes argumanıyla yüklüyoruz
ENV DEBIAN_FRONTEND=noninteractive
RUN apt install --assume-yes apt-utils
# artık apt install -y gerekmeyecek
RUN apt install nano \
vi \
vim

Docker yansısını oluştururken kullanıcı eklemek

Kullanıcı eklemek için şu makelemdekiKullanıcı Ekleme (userAdd mi, addUser mı?)” başlığına göz atabilirsiniz. Docker yansısını oluştururken kullanıcı etkileşim istemeyeceğimiz için -D anahtarını kullanabiliriz.

FROM alpine
RUN adduser -D cem
USER cem

Yukarıdaki addUser komutu yerine userAdd ile ev dizini yaratmak, kullanıcının hangi kabuğu kullanmasını istiyorsanız belirtebilirsiniz (varsayılan olarak sh’a düşmek ve sürekli bash ile kabuk değiştirmek istemeyebilirsiniz)

FROM alpine
RUN useradd -m -d /ev_dizinleri/cemin_evi --shell /bin/bash cem
USER cem
WORKDIR /ev_dizinleri/cemin_evi

Artık docker kapsayıcıya bağlanmak istediğinizde doğrudan cem kullanıcısının bash kabuğuyla açıldığını göreceksiniz:

docker run -i -t kalipAdi
cem@463256ae6609:~$
https://stackoverflow.com/a/49848507/104085
RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1001 ubuntu
USER ubuntu
WORKDIR /home/ubuntu

Konteynerler Arasında İletişim

1) --link ile

Uygulamanız içinden bağlanacağınız sunucunun adını (host name), sunucunun konteynerine takma ad olarak verebilirsiniz.

Aşağıdaki örnekte soldaki pencerede çalışan redis sunucusunun konteyner adı redisSunucu olmasına rağmen sağ pencerede istemci görevi görecek olan konteyner içindeki (farzedlim) uygulamanın içinde redisDb sunucu adıyla erişmek istiyor.

İstemci olacak konteyneri aşağıdaki arguman ile başlatırız

--link konteynerinGercekAdı:baglanacakKonteynerIcinGecerliTakmaAd
# docker run --name redisSunucu redis
# docker run --link redisSunucu:redisDb -it travelping/nettools bash

Yukarıdaki şekilde konteynerleri ayaklandırıp nc ile sanki istemci gibi bağlantı kurmak isteyelim:

bash-5.0# nc -vv redisDb 6379
bash-5.0# nc -vv redisSunucu 6379

2) Özel ağ yaratıp, konteynerleri bu ağda koşturarak

Aşağıdaki 3 ekran görüntüsü işin tamamını veriyor ama tek tek anlatımı için okumaya incelemeye devam.

Önce redis konteyner oluşturalım. 
Başlangıçta belirtmezsek bridge ağına dahil olacak.
$ docker run -d --name redisSunucu redis
Sadece belirli konteynerlerin kullanacağı bir ağ yaratalım
$ docker network create --driver bridge YALITILMIS_AG
Ağları listeleyelim
$ docker network ls
redisSunucu Konteynerini YALITILMIŞ_AĞ isimli ağa dahil edelim 
$ docker network connect YALITILMIS_AG redisSunucu
redisSunucu isimli kapsayıcının ağ bilgilerini görelim
$ docker ps --filter name=redisSunucu --format "table {{.Names}} \t {{.Networks}} \t {{.Ports}} \t {{.Status}}"
bridge Ağından çıkaralım
$ docker network disconnect bridge redisSunucu
redisSunucu isimli kapsayıcının ağ bilgilerini görelim
$ docker ps --filter name=redisSunucu --format "table {{.Names}} \t {{.Networks}} \t {{.Ports}} \t {{.Status}}"
redisSunucu konteynerinin IP adresine bakalım. 20'li Blok içinde. Yani YALITILMIŞ_AĞ dediğimiz network için 172.20.0.xxx bloğu 
$ docker inspect redisSunucu | grep IP

YALITILMIS_AG isimli ağı incelediğimizde 172.20'li bloğu ve içinde iki konteyner olduğunu görüyoruz

Şimdi aynı ağa dahil olacak bir konteyner ile ping atalım redisSunucuya
$ docker run -it --net=YALITILMIS_AG travelping/nettools bash
Bir de aynı ağda olmayacak bir konteyner ile ping atalım
$ docker run -it --net=bridge travelping/nettools bash

multi-stage Dockerfile İçinde ARG tanımlamak

# multi-stage Dockerfile içinde tüm aşamalarda kullanılsın diye 
# en üstte tanımlıyoruz.
#
# Böylece "docker build --build-arg kuresel=hede..." komutunda gelen
# argumanları
# 1) FROM aşamalarında sadece FROM etiketlerinde kullanmak
# için (ALPINE_VERSION gibi) fazladan tanımlamaya gerek yok.
#
# 2) Ancak aşamaların içinde de kullanacaksak tekrar KURESEL
# argumanı gibi tanımlamalıyız!
ARG KURESEL
ARG ALPINE_VERSION=3.6
FROM alpine:${ALPINE_VERSION}
ARG KURESEL
RUN [ -z "$KURESEL" ] && echo "KURESEL is required" && exit 1 || true
RUN test -n "${KURESEL}" || (echo "KURESEL not set" && false)
CMD echo "oldi mi?"
FROM alpine:${ALPINE_VERSION}
ARG KURESEL
CMD echo "ula haçan ne diysun?"

build aşamasında argümanların kontrol edilmesi *

  • -n : Non-empty string
  • -z : Zero length
    [ -z “$var” ] && echo “Empty”
    [ -z “$var” ] && echo “Empty” || echo “Not empty”
$ AL=""; [ -z "$AL" ] && echo "boş: $AL" || echo "boş değil: $AL"
boş:
$ AL="1"; [ -z "$AL" ] && echo "boş: $AL" || echo "boş değil: $AL"
boş değil: 1
$ AL=""; [ ! -n "$AL" ] && echo "boş: $AL" || echo "boş değil: $AL"
boş:
$ AL="1"; [ ! -n "$AL" ] && echo "boş: $AL" || echo "boş değil: $AL"
boş değil: 1

Docker yansısını oluştururken eksik veya geçersiz argümanları tespit edip derleme sürecini kesmek için betik içinde test komutlarını çalıştırıp exit ile docker build sürecini kırabilirsiniz

FROM alpine:3.6
ARG KURESEL
RUN [ -z "$KURESEL" ] && echo "KURESEL is required" && exit 1 || true
RUN test -n "${KURESEL}" || (echo "KURESEL not set" && false)

Aşağıda tam örnek mevcut:

ARG ARG_1
ARG ARG_2
FROM alpine
ARG ARG_1
ARG ARG_2
RUN [ -n "${ARG_1}" ] || (echo "ARG_1 Boş Olamaz!" && false)
RUN [ ! -z "${ARG_2}" ] || (echo "ARG_2 Boş Olamaz!" && false)
CMD ["echo", "buraya geldi"]
$ docker build --build-arg ARG_1=bir --build-arg ARG_2=iki -t arg-check -f .\Dockerfile.check-arg .

ubuntu:xenial ile Servisleri çalıştırılması

# docker run --privileged --entrypoint "/sbin/init" ubuntu:xenial

Ubuntu’nun xenial sürümü (16.04 oluyor) yoğunlukla kullandığım sürüm olduğu için öne alıyorum, belki bionic (18.04) veya focal (20.04) için de durum aynıdır.

/sbin/init konteynerin çalışacak ilk komutu olmalı ki; hizmetleri ilk işlem olarak (PID 1) başlatmış olalım.

Bunun için root kullanıcı yetkisi gerektiğinden --privileged argümanını ekliyorum.

Artık hizmetler başlatıldığı için varsa yüklü ve faal (enable işaretlenmiş) bir hizmet, çalışacaktır.

Health Check *

Hizmetimizin adı cnrnrf ve başaltıldığında 8009 portunu dinleyecek. Eğer bu portu dinleyen biri yoksa bizim için konteyner sağlıksız çalışıyor demektir. Bu yüzden health-cmd argümanına 8009 dinleniyor mu diye bakacağız.

$ docker run --name t1 -d --privileged --health-cmd='netstat -pltn | grep 8009 || exit 1' --health-interval=1s cinar/nrf

Şimdi konteyner içinde koşan hizmeti durduralım ve sağlık durumuna bakalım:

$ docker exec -it t1 service cnrnrf stop
8009 portunu dinleyen bir uygulama olmadığı için “sağlıksız” olarak işaretlendi…!

Şimdi hizmeti tekrar başlatalım ve konteyner’in sağlıklı duruma geldiğini görelim:

$ docker exec -it t1 service cnrnrf start

Konsol üstünden sağlık durumuyla ilgili bilgi almak için:

$ sleep 2; docker inspect --format='{{json .State.Health}}' test
{
"Status": "unhealthy",
"FailingStreak": 3,
"Log": [
{
"Start": "2016-05-25T17:22:04.635478668Z",
"End": "2016-05-25T17:22:04.7272552Z",
"ExitCode": 0,
"Output": " File: /etc/passwd\n Size: 334 \tBlocks: 8 IO Block: 4096 regular file\nDevice: 32h/50d\tInode: 12 Links: 1\nAccess: (0664/-rw-rw-r--) Uid: ( 0/ root) Gid: ( 0/ root)\nAccess: 2015-12-05 22:05:32.000000000\nModify: 2015..."
}
,...
]
}

Health Check ile “docker-compose up” komutunun STATUS CODE bilgisini 0'dan farklı hale getirmek

Özetle aşağıdaki gibi kontrol etmek istediğim nf konteynerini fazladan netcat paketi kurmak yerine ikinci bir hizmet olan nfHealth ile kontrol etmek, ve üçüncü bir hizmet olan nfHealthDepend ile docker-compose up komutunun çıkış kodunu bu bağımlılıkla 0 veya 1 olarak alabilmek.

Önce nfHealth konteynerinde kullandığım amouat/network-utils yansısından yaratılacak olan konteyneri nasıl health check süresince ayakta tutabileceğimize bakalım:

Neden Health Check Konteynerine entrypoint veriyoruz?

amouat/network-utils Yansısının CMD ve ENTRYPOINT özelliklerine dikkat edelim:

..................
"Cmd": [
"bash"
],
..................
"Entrypoint": null
..................
docker inspect amouat/network-utils — format “{{json .Config}}” | jq .

amouat/network-utils Yansısından konteyner oluşturduğumuzda kendi konsolumuzu -t (terminal) argümanıyla bağlamazsak (ki -i argümansız sadece imleçin yanıp söndüğünü görürüz) bash komutu çalışır ve çıkar.

Health Check yaparken nfHealth hizmetini sunan konteyneri isürekli çalışır halde tutabilmek için entrypoint komutu olarak tail -f /dev/null ile sonsuz boşluğun kuyruğuna takılıyoruz.

Eğer yeni bir entrypoint vermezsek ENTRYPOINT=null, CMD=bash olduğu için hiç health check yapamadan çıkacak!

nf hizmetinin konteynerinin ip adresine 8009 portundan erişim sağlandığı için health check sağlıklı bitiyor ve bağımlılığı olan konteyner de başarıyla ayaklanıyor. Böylece docker-compose up komutu 0 çıkış koduyla bitiyor.

Bir de başarısız olarak çıkış yapalım:

nf Hizmetinin konteyneri 10.10.21.11 ip adresiyle ayaklanıyor ama 9009 portunu dinleyen bir proses olmadığı için
nfHealth hizmetinin konteyneri health check komutu olan
nc -vz 10.10.21.11 9009
ile bağlantı kuramıyor ve ExitCode olarak 1 ile sonlanıyor.

Sonuçta; nfHealth hizmetinin konteyneri sağlıksız (unhealthy) olarak işaretlenince,
nfHealthDepend hizmetinin konteyneri de nfHealth konteynerine service_healthy şartıyla bağlı olduğu için başlatılamıyor

Ve docker-compose up komutu hata kodu 1 ile bitiyor.

Could not resolve ‘security.ubuntu.com’

=> CACHED [20/22] RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu xenial stable" > /etc/apt/sources.list.d/docker.list                                     0.0s 
=> ERROR [21/22] RUN apt-get update && apt-get install -y docker-ce-cli 2.1s
------
> [21/22] RUN apt-get update && apt-get install -y docker-ce-cli:
#25 0.479 Err:1 http://security.ubuntu.com/ubuntu xenial-security InRelease
#25 0.479 Could not resolve 'security.ubuntu.com'
#25 0.678 Hit:2 http://archive.ubuntu.com/ubuntu xenial InRelease
#25 0.753 Get:3 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [109 kB]
#25 1.045 Get:4 http://archive.ubuntu.com/ubuntu xenial-backports InRelease [107 kB]
#25 1.211 Fetched 216 kB in 0s (254 kB/s)
#25 1.211 Reading package lists...
#25 2.093 E: The method driver /usr/lib/apt/methods/https could not be found.
#25 2.093 W: Failed to fetch http://security.ubuntu.com/ubuntu/dists/xenial-security/InRelease Could not resolve 'security.ubuntu.com'
#25 2.093 E: Failed to fetch https://download.docker.com/linux/ubuntu/dists/xenial/InRelease
#25 2.093 W: Some index files failed to download. They have been ignored, or old ones used instead.
------
failed to solve with frontend dockerfile.v0: failed to build LLB: executor failed running [/bin/sh -c apt-get update && apt-get install -y docker-ce-cli]: runc did not terminate sucessfully

Docker yansısı oluştururken /etc/resolv.conf dosyasına yeni bir name server (ad sunucusu yani alan adını IP adresine çeviren bir sunucunun adresini) ekleyemiyoruz.

Eğer bu sanal ve fiziki bir linux sunucusu olsaydı /etc/resolv.conf dosyasını şu şekilde değiştirerek çözümlemeyi sağlayabilirdik:

nameserver 8.8.8.8 
nameserver 8.8.4.4

Şimdi docker konteyner içinden bir başarılı bir başarısız alan adını IP adresine dönüştürme işini yapalım:

c:\> docker run busybox nslookup google.com
c:\> docker run busybox nslookup security.ubuntu.com

İki türlü çözebiliyoruz :

  1. docker build yapıyorsak sadece add-host parametresiyle
  2. docker build veya docker run yapıyorsak hem add-host hem dns parametreleriyle
docker build --ad-host google.com:142.250.187.110 -f ......

Konteyneri çalıştırırken:

docker run --ad-host google.com:142.250.187.110 busyboxveya docker run --dns 176.31.121.197 busybox

Docker Konteyner İçine docker-ce-cli Yüklemek

Docker komutlarını istemci aracıyla çalıştırıyor ve bu işlemlerin sonuçlarını bir docker sunucu (host) meydana getiriyordu.

Artık docker istemci araçları ayrı bir paket olarak kurulabiliyor.

Docker istemcisini (docker-ce-cli) kurmak için önce bu paketi indirebileceğimiz paket sunucusunu ve bu sunucuya yetkilendirilmek için PGP PUBLIC KEY BLOCK dosyasını indirip bu sunucunun ayar dosyasına yazıyoruz. HTTPS bağlantılarında sorun yaşamamak için apt-transport-https paketini de kurarak istemci paketimizi (docker-ce-cli) yüklüyoruz.

RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu xenial stable" > /etc/apt/sources.list.d/docker.list
RUN apt-get install -y apt-transport-https
RUN apt-get update
RUN apt-get install -y docker-ce-cli

DOCKER_HOST eğer ortam değişkenlerinde tanımlı değilse unix://var/run/docker.sock dosyasından okumak isteyecektir. Ya bu dosyayı host dosyasından bağlayacaksınız:

docker run ... -v /var/run/docker.sock:/var/run/docker.sock ...

Ya da ortam değişkenlerinde istediğiniz docker servisinin çalıştığı sunucuyu hedef olarak vereceksiniz:

export DOCKER_HOST=tcp://host.docker.internal:2375

Burada host makinadaki docker hizmetini vermek için tcp://host.docker.internal:2375 bilgisini DOCKER_HOST değişkenine yazıyoruz.

--

--

Cem Topkaya
Cem Topkaya

Written by Cem Topkaya

Evlat, kardeş, ağabey, eş, baba, müzik sever, öğrenmek ister, paylaşmaya can atar, iyi biri olmaya çalışır, hakkı geçenlerden helallik ister vs.

No responses yet