Fuori dal coro

Fuori dal coro dico
che la più grande battaglia della nostra epoca è quella che combattiamo contro l’ego.
Abbiamo sempre questo spasmodico bisogno di riempirlo .
Ignorando che è proprio lui la principale causa dei nostri malesseri .
E i social non fanno altro che amplificare tutto questo .
Ricevere un complimento sulla tua immagine sul web non aggiunge alcun valore alla persona ( le cui qualità continuano a rimanere sconosciute ai più che ti mettono il tanto sospirato like )

La più grande battaglia della nostra epoca è quella che combattiamo contro l’ego

Luigi

La parola ego significa “ io”. Rappresenta la coscienza di chi siamo . È quello che facciamo che definisce chi siamo . Riempire l ego con ciò che facciamo è positivo .
Questo ego positivo è minato costantemente dal bisogno di approvazione virtuale .
Normalmente una persona non va in giro fermando sconosciuti e chiedendogli se a 50 anni è ancora figo, o se piace la nuova maglietta attillata.

Sarebbe strano .. e comunque inutile . Gli uomini e le donne per noi belli (anche quando non lo sono esteticamente) si riconoscono quasi chimicamente. A volte è un semplice sorriso.
L’autostima è quindi costantemente minata sul web se ci si propone con un aspetto superficiale come l’immagine .
Alimentando il bisogno di pubblicare sempre più immagini . Alimentiamo il bisogno di sentirci degli “influencer”, che poi tra filtri, ciglia finte, pose strategiche, grand’angoli non siamo neanche noi ma una proiezione di noi, a volte una caricatura.


L’uomo e la donna hanno dei bisogni . Il bisogno nasce da una mancanza.


Se una persona pubblica 5 /6 foto di sè tutte in fila chiedendo approvazione .. è perché ne ha bisogno .. quindi manca. Manca autostima appunto . E ha bisogno del consenso .
Da qui nascono selfie in tutte le pose , tag a posti dove non si è nemmeno stati , foto con amici a significare una vita mondana anche quando invece sono le persone più sole del mondo: l’ego vero si nutre di verità . Per quanto imperfetti e poco piacevoli possiamo essere … quanto è bella la verità !!!

😘😘
Ma questo è un altro discorso ..

Linux Name spaces

Namespaces in Linux are heavily used by many applications, e.g. LXC, Docker and Openstack.
Question: How to find all existing namespaces in a Linux system?

The answer is quite difficult, because it’s easy to hide a namespace or more exactly make it difficult to find them.

Exploring the system

In the basic/default setup Ubuntu 12.04 and higher provide namespaces for

  • ipc for IPC objects and POSIX message queues
  • mnt for filesystem mountpoints
  • net for network abstraction (VRF)
  • pid to provide a separated, isolated process ID number space
  • uts to isolate two system identifiers — nodename and domainname – to be used by uname

These namespaces are shown for every process in the system. if you execute as rootls -lai /proc/1/nsShell

ls -lai /proc/1/ns
 
60073292 dr-x--x--x 2 root root 0 Dec 15 18:23 .
   10395 dr-xr-xr-x 9 root root 0 Dec  4 11:07 ..
60073293 lrwxrwxrwx 1 root root 0 Dec 15 18:23 ipc -> ipc:[4026531839]
60073294 lrwxrwxrwx 1 root root 0 Dec 15 18:23 mnt -> mnt:[4026531840]
60073295 lrwxrwxrwx 1 root root 0 Dec 15 18:23 net -> net:[4026531968]
60073296 lrwxrwxrwx 1 root root 0 Dec 15 18:23 pid -> pid:[4026531836]
60073297 lrwxrwxrwx 1 root root 0 Dec 15 18:23 uts -> uts:[4026531838]

you get the list of attached namespaces of the init process using PID=1. Even this process has attached namespaces. These are the default namespaces for ipc, mnt, net, pid and uts. For example, the default net namespace is using the ID net:[4026531968]. The number in the brackets is a inode number.

In order to find other namespaces with attached processes in the system, we use these entries of the PID=1 as a reference. Any process or thread in the system, which has not the same namespace ID as PID=1 is not belonging to the DEFAULT namespace.

Additionally, you find the namespaces created by „ip netns add <NAME>“ by default in /var/run/netns/ .

The python code

The python code below is listing all non default namespaces in a system. The program flow is

  • Get the reference namespaces from the init process (PID=1). Assumption: PID=1 is assigned to the default namespaces supported by the system
  • Loop through /var/run/netns/ and add the entries to the list
  • Loop through /proc/ over all PIDs and look for entries in /proc/<PID>/ns/ which are not the same as for PID=1 and add then to the list
  • Print the result

List all non default namespaces in a systemPython

#!/usr/bin/python
#
# List all Namespaces (works for Ubuntu 12.04 and higher)
#
# (C) Ralf Trezeciak    2013-2014
#
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
import os
import fnmatch
 
if os.geteuid() != 0:
    print "This script must be run as root\nBye"
    exit(1)
 
def getinode( pid , type):
    link = '/proc/' + pid + '/ns/' + type
    ret = ''
    try:
        ret = os.readlink( link )
    except OSError as e:
        ret = ''
        pass
    return ret
 
#
# get the running command
def getcmd( p ):
    try:
        cmd = open(os.path.join('/proc', p, 'cmdline'), 'rb').read()
        if cmd == '':
            cmd = open(os.path.join('/proc', p, 'comm'), 'rb').read()
        cmd = cmd.replace('\x00' , ' ')
        cmd = cmd.replace('\n' , ' ')
        return cmd
    except:
        return ''
#
# look for docker parents
def getpcmd( p ):
    try:
        f = '/proc/' + p + '/stat'
        arr = open( f, 'rb').read().split()
        cmd = getcmd( arr[3] )
        if cmd.startswith( '/usr/bin/docker' ):
            return 'docker'
    except:
        pass
    return ''
#
# get the namespaces of PID=1
# assumption: these are the namespaces supported by the system
#
nslist = os.listdir('/proc/1/ns/')
if len(nslist) == 0:
    print 'No Namespaces found for PID=1'
    exit(1)
#print nslist
#
# get the inodes used for PID=1
#
baseinode = []
for x in nslist:
    baseinode.append( getinode( '1' , x ) )
#print "Default namespaces: " , baseinode
err = 0
ns = []
ipnlist = []
#
# loop over the network namespaces created using "ip"
#
try:
    netns = os.listdir('/var/run/netns/')
    for p in netns:
        fd = os.open( '/var/run/netns/' + p, os.O_RDONLY )
        info = os.fstat(fd)
        os.close( fd)
        ns.append( '-- net:[' + str(info.st_ino) + '] created by ip netns add ' + p )
        ipnlist.append( 'net:[' + str(info.st_ino) + ']' )
except:
    # might fail if no network namespaces are existing
    pass
#
# walk through all pids and list diffs
#
pidlist = fnmatch.filter(os.listdir('/proc/'), '[0123456789]*')
#print pidlist
for p in pidlist:
    try:
        pnslist = os.listdir('/proc/' + p + '/ns/')
        for x in pnslist:
            i = getinode ( p , x )
            if i != '' and i not in baseinode:
                cmd = getcmd( p )
                pcmd = getpcmd( p )
                if pcmd != '':
                    cmd = '[' + pcmd + '] ' + cmd
                tag = ''
                if i in ipnlist:
                    tag='**' 
                ns.append( p + ' ' + i + tag + ' ' + cmd)
    except:
        # might happen if a pid is destroyed during list processing
        pass
#
# print the stuff
#
print '{0:>10}  {1:20}  {2}'.format('PID','Namespace','Thread/Command')
for e in ns:
    x = e.split( ' ' , 2 )
    print '{0:>10}  {1:20}  {2}'.format(x[0],x[1],x[2][:60])
#

Copy the script to your system as listns.py , and run it as root using python listns.py

       PID  Namespace             Thread/Command
        --  net:[4026533172]      created by ip netns add qrouter-c33ffc14-dbc2-4730-b787-4747
        --  net:[4026533112]      created by ip netns add qrouter-5a691ed3-f6d3-4346-891a-3b59
        --  net:[4026533050]      created by ip netns add qdhcp-02e848cb-72d0-49df-8592-2f7a03
        --  net:[4026532992]      created by ip netns add qdhcp-47cfcdef-2b34-43b8-a504-6720e5
       297  mnt:[4026531856]      kdevtmpfs 
      3429  net:[4026533050]**    dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3429  mnt:[4026533108]      dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3446  net:[4026532992]**    dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3446  mnt:[4026533109]      dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3486  net:[4026533050]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      3486  mnt:[4026533107]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      3499  net:[4026532992]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      3499  mnt:[4026533110]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      4117  net:[4026533112]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      4117  mnt:[4026533169]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
     41998  net:[4026533172]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
     41998  mnt:[4026533229]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil

The example above is from an Openstack network node. The first four entries are entries created using the command ip. The entry PID=297 is a kernel thread and no user process. All other processes listed, are started by Openstack agents. These process are using network and mount namespaces. PID entries marked with ‚**‘ have a corresponding entry created with the ip command.

When a docker command is started, the output is:

PID  Namespace             Thread/Command
        --  net:[4026532676]      created by ip netns add test
        35  mnt:[4026531856]      kdevtmpfs 
      6189  net:[4026532585]      [docker] /bin/bash 
      6189  uts:[4026532581]      [docker] /bin/bash 
      6189  ipc:[4026532582]      [docker] /bin/bash 
      6189  pid:[4026532583]      [docker] /bin/bash 
      6189  mnt:[4026532580]      [docker] /bin/bash 

The docker child running in the namespaces is marked using [docker].

On a node running mininet and a simple network setup the output looks like :

 exampleShell
       PID  Namespace             Thread/Command
        14  mnt:[4026531856]      kdevtmpfs 
      1198  net:[4026532150]      bash -ms mininet:h1 
      1199  net:[4026532201]      bash -ms mininet:h2 
      1202  net:[4026532252]      bash -ms mininet:h3 
      1203  net:[4026532303]      bash -ms mininet:h4

Googles Chrome Browser

Googles Chrome Browser makes extensive use of the linux namespaces. Start Chrome and run the python script. The output looks like:Chrome’s namespaces

 PID  Namespace             Thread/Command
        63  mnt:[4026531856]      kdevtmpfs 
     30747  net:[4026532344]      /opt/google/chrome/chrome --type=zygote 
     30747  pid:[4026532337]      /opt/google/chrome/chrome --type=zygote 
     30753  net:[4026532344]      /opt/google/chrome/nacl_helper 
     30753  pid:[4026532337]      /opt/google/chrome/nacl_helper 
     30754  net:[4026532344]      /opt/google/chrome/chrome --type=zygote 
     30754  pid:[4026532337]      /opt/google/chrome/chrome --type=zygote 
     30801  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30801  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30807  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30807  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30813  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30813  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30820  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30820  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30829  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30829  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30835  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30835  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30841  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30841  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30887  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30887  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30893  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30893  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30901  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30901  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30910  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30910  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30915  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30915  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30923  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30923  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30933  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30933  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30938  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30938  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30944  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30944  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31271  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31271  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31538  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31538  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for

Chrome makes use of pid and network namespaces to restrict the access of subcomponents. The network namespace does not have a link in /var/run/netns/.

Conclusion

It’s quite hard to explore the Linux namespace. There is a lot of documentation flowing around. I did not find any simple program to look for namespaces in the system. So I wrote one.

The script cannot find a network namespace, which do not have any process attached to AND which has no reference in /var/run/netns/. If root creates the reference inode somewhere else in the filesystem, you may only detect network ports (ovs port, veth port on one side), which are not attached to a known network namespace –> an unknown guest might be on your system using a „hidden“ (not so easy to find) network namespace.

And — Linux namespaces can be stacked.

Per te (per me)

Qualunque fiore tu sia, quando verrà il tuo tempo, sboccerai. Prima di allora una lunga e fredda notte potrà passare. Anche dai sogni della notte trarrai forza e nutrimento. Perciò sii paziente verso quanto ti accade e curati e amati senza paragonarti o voler essere un altro fiore, perché non esiste fiore migliore di quello che si apre nella pienezza di ciò che è. E quando ciò accadrà, potrai scoprire che andavi sognando di essere un fiore che aveva da fiorire.“ —  Daisaku Ikeda

Tutorial: creazione di un cluster con un’attività Fargate utilizzando la CLI di Amazon ECS

Questo tutorial mostra come configurare un cluster e distribuire un servizio con attività che utilizzano il tipo di lancio Fargate.

Prerequisiti

Verifica i seguenti requisiti preliminari:

Fase 1: Crea il ruolo IAM per l’esecuzione dell’attività

L’agente del container Amazon ECS effettua chiamate all’API di AWS per tuo conto, pertanto richiede una policy e un ruolo IAM che consentano al servizio di stabilire che l’agente appartiene a te. Questo ruolo IAM viene definito un ruolo IAM di esecuzione delle attività. Se disponi già di un ruolo per l’esecuzione delle attività pronto per essere utilizzato, puoi ignorare questa fase. Per ulteriori informazioni, consulta Ruolo IAM per l’esecuzione di attività Amazon ECS.

Per creare il ruolo IAM per l’esecuzione delle attività utilizzando AWS CLI

  1. Crea un file denominato task-execution-assume-role.json con i seguenti contenuti:{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
  2. Crea il ruolo per l’esecuzione delle attivitàaws iam --region us-west-2 create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://task-execution-assume-role.json
  3. Collega la policy relativa al ruolo per l’esecuzione delle attività:aws iam --region us-west-2 attach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

Fase 2: Configura la CLI di Amazon ECS

Per poter effettuare richieste API a tuo nome, la CLI di Amazon ECS necessita delle credenziali, che può estrarre da variabili di ambiente, da un profilo AWS o da un profilo Amazon ECS. Per ulteriori informazioni, consulta Configurazione della CLI di Amazon ECS.

Per creare una configurazione della CLI di Amazon ECS

  1. Crea una configurazione cluster, che definisce la regione AWS, i prefissi di creazione delle risorse e il nome del cluster da utilizzare con la CLI di Amazon ECS:ecs-cli configure --cluster tutorial --default-launch-type FARGATE --config-name tutorial --region us-west-2
  2. Crea un profilo CLI utilizzando l’ID chiave di accesso e la chiave segreta:ecs-cli configure profile --access-key AWS_ACCESS_KEY_ID --secret-key AWS_SECRET_ACCESS_KEY --profile-name tutorial-profile

Fase 3: creare un cluster e configurare il gruppo di sicurezza

Per creare un cluster ECS e un gruppo di sicurezza

  1. Creare un cluster Amazon ECS con il comando ecs-cli up. Poiché nella configurazione del cluster hai specificato Fargate come tipo di lancio predefinito, il comando crea un cluster vuoto e un VPC configurato con due sottoreti pubbliche.ecs-cli up --cluster-config tutorial --ecs-profile tutorial-profilePossono essere necessari alcuni minuti perché le risorse vengano create e il comando venga completato. L’output di questo comando contiene gli ID di sottorete e i VPC creati. Prendi nota di questi ID poiché verranno utilizzati in un secondo momento.
  2. Utilizzando l’AWS CLI, recupera l’ID del gruppo di sicurezza predefinito per il VPC. Utilizza l’ID VPC dell’output precedente:aws ec2 describe-security-groups --filters Name=vpc-id,Values=VPC_ID --region us-west-2L’output di questo comando contiene l’ID del gruppo di sicurezza, utilizzato nella fase successiva.
  3. Tramite l’AWS CLI, aggiungi una regola del gruppo di sicurezza per consentire l’accesso in entrata sulla porta 80:aws ec2 authorize-security-group-ingress --group-id security_group_id --protocol tcp --port 80 --cidr 0.0.0.0/0 --region us-west-2

Fase 4: Crea un file Compose

In questa fase dovrai generare un semplice file Docker Compose che crea un’applicazione Web PHP. Attualmente, la CLI di Amazon ECS supporta le versione 1, 2 e 3 della sintassi del file di Docker Compose. Questo tutorial utilizza Docker Compose v3.

Di seguito è riportato il file di Compose, che puoi denominare docker-compose.yml. Il container web espone la porta 80 per il traffico in entrata al server Web, oltre a configurare il posizionamento dei log di container nel gruppo di log CloudWatch creato in precedenza. Questa riportata è la best practice per le attività Fargate.

version: '3'
services:
  web:
    image: amazon/amazon-ecs-sample
    ports:
      - "80:80"
    logging:
      driver: awslogs
      options: 
        awslogs-group: tutorial
        awslogs-region: us-west-2
        awslogs-stream-prefix: web

Nota

Se il tuo account contiene già un gruppo di log CloudWatch Logs denominato tutorial nella regione us-west-2, scegli un nome univoco in modo che l’interfaccia a riga di comando ECS crei un nuovo gruppo di log per questo tutorial.

Oltre alle informazioni del file di Docker Compose, dovrai specificare alcuni parametri specifici di Amazon ECS necessari per il servizio. Utilizzando gli ID per VPC, sottorete e gruppo di sicurezza ottenuti nel passo precedente, crea un file denominato ecs-params.yml con il seguente contenuto:

version: 1
task_definition:
  task_execution_role: ecsTaskExecutionRole
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 0.5GB
    cpu_limit: 256
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - "subnet ID 1"
        - "subnet ID 2"
      security_groups:
        - "security group ID"
      assign_public_ip: ENABLED

Fase 5: Distribuisci il file Compose a un cluster

Dopo aver creato il file Compose, puoi distribuirlo al cluster con il comando ecs-cli compose service up. Per impostazione predefinita, il comando cerca i file denominati docker-compose.yml ed ecs-params.yml nella directory corrente; puoi specificare un altro file di Docker Compose con l’opzione --file e un altro file ECS Params con l’opzione --ecs-params. Per impostazione predefinita, le risorse create da questo comando contengono la directory corrente nel titolo, ma puoi sostituire questo valore con l’opzione --project-name. L’opzione --create-log-groups crea i gruppi di log CloudWatch per i log di container.

ecs-cli compose --project-name tutorial service up --create-log-groups --cluster-config tutorial --ecs-profile tutorial-profile

Fase 6: Visualizza i container in esecuzione su un cluster

Dopo aver distribuito il file Compose, puoi visualizzare i container in esecuzione nel servizio con il comando ecs-cli compose service ps.

ecs-cli compose --project-name tutorial service ps --cluster-config tutorial --ecs-profile tutorial-profile

Output:

Name                                           State    Ports                     TaskDefinition  Health
tutorial/0c2862e6e39e4eff92ca3e4f843c5b9a/web  RUNNING  34.222.202.55:80->80/tcp  tutorial:1      UNKNOWN

Nell’esempio precedente, dal file Compose puoi visualizzare sia il container web, sia l’indirizzo IP e la porta del server Web. Se il browser Web fa riferimento a tale indirizzo, viene visualizzata l’applicazione Web PHP. Nell’output è riportato anche il valore task-id del container. Copia l’ID attività, che ti servirà nella fase successiva.

Fase 7: Visualizza i log di container

Visualizza i log per l’attività:

ecs-cli logs --task-id 0c2862e6e39e4eff92ca3e4f843c5b9a --follow --cluster-config tutorial --ecs-profile tutorial-profile

Nota

L’opzione --follow indica alla CLI di Amazon ECS di eseguire continuamente il polling per i log.

Fase 8: Dimensiona le attività sul cluster

Con il comando ecs-cli compose service scale puoi ampliare il numero di attività per aumentare il numero di istanze dell’applicazione. In questo esempio, il conteggio in esecuzione dell’applicazione viene portato a due.

ecs-cli compose --project-name tutorial service scale 2 --cluster-config tutorial --ecs-profile tutorial-profile

Ora nel cluster saranno presenti due container in più:

ecs-cli compose --project-name tutorial service ps --cluster-config tutorial --ecs-profile tutorial-profile

Output:

Name                                           State    Ports                      TaskDefinition  Health
tutorial/0c2862e6e39e4eff92ca3e4f843c5b9a/web  RUNNING  34.222.202.55:80->80/tcp   tutorial:1      UNKNOWN
tutorial/d9fbbc931d2e47ae928fcf433041648f/web  RUNNING  34.220.230.191:80->80/tcp  tutorial:1      UNKNOWN

Fase 9: visualizzare l’applicazione Web

Inserisci l’indirizzo IP dell’attività nel browser Web per visualizzare una pagina Web contenente l’applicazione Web Simple PHP App (App PHP semplice).

Fase 10: Elimina

Al termine di questo tutorial, dovrai eliminare le risorse in modo che non comportino ulteriori addebiti. Per prima cosa, elimina il servizio: in questo modo i container esistenti verranno interrotti e non tenteranno di eseguire altre attività.

ecs-cli compose --project-name tutorial service down --cluster-config tutorial --ecs-profile tutorial-profile

Ora arresta il cluster per eliminare le risorse create in precedenza con il comando ecs-cli up.

ecs-cli down --force --cluster-config tutorial --ecs-profile tutorial-profile

Ready-to-use commands and tips for kubectl

Image for post

Kubectl is the most important Kubernetes command-line tool that allows you to run commands against clusters. We at Flant internally share our knowledge of using it via formal wiki-like instructions as well as Slack messages (we also have a handy and smart search engine in place — but that’s a whole different story…). Over the years, we have accumulated a large number of various kubectl tips and tricks. Now, we’ve decided to share some of our cheat sheets with a wider community.

I am sure our readers might be familiar with many of them. But still, I hope you will learn something new and, thereby, improve your productivity.

NB: While some of the commands & techniques listed below were compiled by our engineers, others were found on the Web. In the latter case, we checked them thoroughly and found them useful.

Well, let’s get started!

Getting lists of pods and nodes

1. I guess you are all aware of how to get a list of pods across all Kubernetes namespaces using the --all-namespaces flag. Many people are so used to it that they have not noticed the emergence of its shorter version, -A (it exists since at least Kubernetes 1.15).

2. How do you find all non-running pods (i.e., with a state other than Running)?

kubectl get pods -A --field-selector=status.phase!=Running | grep -v Complete
Image for post

By the way, examining the --field-selector flag more closely (see the relevant documentation) might be a good general recommendation.

3. Here is how you can get the list of nodes and their memory size:

kubectl get no -o json | \
jq -r '.items | sort_by(.status.capacity.memory)[]|[.metadata.name,.status.capacity.memory]| @tsv'
Image for post

4. Getting the list of nodes and the number of pods running on them:

kubectl get po -o json --all-namespaces | \
jq '.items | group_by(.spec.nodeName) | map({"nodeName": .[0].spec.nodeName, "count": length}) | sort_by(.count)'
Image for post

5. Sometimes, DaemonSet does not schedule a pod on a node for whatever reason. Manually searching for them is a tedious task, so here is a mini-script to get a list of such nodes:

ns=my-namespace
pod_template=my-pod
kubectl get node | grep -v \"$(kubectl -n ${ns} get pod --all-namespaces -o wide | fgrep ${pod_template} | awk '{print $8}' | xargs -n 1 echo -n "\|" | sed 's/[[:space:]]*//g')\"

6. This is how you can use kubectl top to get a list of pods that eat up CPU and memory resources:

# cpu
kubectl top pods -A | sort --reverse --key 3 --numeric
# memory
kubectl top pods -A | sort --reverse --key 4 --numeric

7. Sorting the list of pods (in this case, by the number of restarts):

kubectl get pods --sort-by=.status.containerStatuses[0].restartCount
Image for post

Of course, you can sort them by other fields, too (see PodStatus and ContainerStatus for details).

Getting other data

1. When tuning the Ingress resource, we inevitably go down to the service itself and then search for pods based on its selector. I used to look for this selector in the service manifest, but later switched to the -o wide flag:

kubectl -n jaeger get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTORjaeger-cassandra ClusterIP None <none> 9042/TCP 77d app=cassandracluster,cassandracluster=jaeger-cassandra,cluster=jaeger-cassandra

As you can see, in this case, we get the selector used by our service to find the appropriate pods.

2. Here is how you can easily print limits and requests of each pod:

kubectl get pods -n my-namespace -o=custom-columns='NAME:spec.containers[*].name,MEMREQ:spec.containers[*].resources.requests.memory,MEMLIM:spec.containers[*].resources.limits.memory,CPUREQ:spec.containers[*].resources.requests.cpu,CPULIM:spec.containers[*].resources.limits.cpu'
Image for post

3. The kubectl run command (as well as createapplypatch) has a great feature that allows you to see the expected changes without actually applying them — the --dry-run flag. When it is used with -o yaml, this command outputs the manifest of the required object. For example:

kubectl run test --image=grafana/grafana --dry-run -o yamlapiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
run: test
name: test
spec:
replicas: 1
selector:
matchLabels:
run: test
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
run: test
spec:
containers:
- image: grafana/grafana
name: test
resources: {}
status: {}

All you have to do now is to save it to a file, delete a couple of system/unnecessary fields, et voila.

NB: Please note that the kubectl run behavior has been changed in Kubernetes v1.18 (now, it generates Pods instead of Deployments). You can find a great summary on this issue here.

4. Getting a description of the manifest of a given resource:

kubectl explain hpaKIND:     HorizontalPodAutoscaler
VERSION: autoscaling/v1DESCRIPTION:
configuration of a horizontal pod autoscaler.FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resourceskind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kindsmetadata <Object>
Standard object metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadataspec <Object>
behaviour of autoscaler. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status.status <Object>
current information about the autoscaler.

Well, that is a piece of extensive and very helpful information, I must say.

Networking

1. Here is how you can get internal IP addresses of cluster nodes:

kubectl get nodes -o json | \
jq -r '.items[].status.addresses[]? | select (.type == "InternalIP") | .address' | \
paste -sd "\n" -
Image for post

2. And this way, you can print all services and their respective nodePorts:

kubectl get --all-namespaces svc -o json | \
jq -r '.items[] | [.metadata.name,([.spec.ports[].nodePort | tostring ] | join("|"))]| @tsv'

3. In situations where there are problems with the CNI (for example, with Flannel), you have to check the routes to identify the problem pod. Pod subnets that are used in the cluster can be very helpful in this task:

kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' | tr " " "\n"
Image for post

Logs

1. Print logs with a human-readable timestamp (if it is not set):

kubectl -n my-namespace logs -f my-pod --timestamps2020-07-08T14:01:59.581788788Z fail: Microsoft.EntityFrameworkCore.Query[10100]

Logs look so much better now, don’t they?

2. You do not have to wait until the entire log of the pod’s container is printed out — just use --tail:

kubectl -n my-namespace logs -f my-pod --tail=50

3. Here is how you can print all the logs from all containers of a pod:

kubectl -n my-namespace logs -f my-pod --all-containers

4. Getting logs from all pods using a label to filter:

kubectl -n my-namespace logs -f -l app=nginx

5. Getting logs of the “previous” container (for example, if it has crashed):

kubectl -n my-namespace logs my-pod --previous

Other quick actions

1. Here is how you can quickly copy secrets from one namespace to another:

kubectl get secrets -o json --namespace namespace-old | \
jq '.items[].metadata.namespace = "namespace-new"' | \
kubectl create-f -

2. Run these two commands to create a self-signed certificate for testing:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=grafana.mysite.ru/O=MyOrganization"
kubectl -n myapp create secret tls selfsecret --key tls.key --cert tls.crt

Helpful links on the topic

In lieu of conclusion — here is a small list of similar publications and cheat sheets’ collections we’ve found online:

Image for post

Deploying WordPress and MySQL with Persistent Volumes in Kubernetes

This tutorial shows you how to deploy a WordPress site and a MySQL database using Minikube. Both applications use PersistentVolumes and PersistentVolumeClaims to store data.

PersistentVolume (PV) is a piece of storage in the cluster that has been manually provisioned by an administrator, or dynamically provisioned by Kubernetes using a StorageClass. A PersistentVolumeClaim (PVC) is a request for storage by a user that can be fulfilled by a PV. PersistentVolumes and PersistentVolumeClaims are independent from Pod lifecycles and preserve data through restarting, rescheduling, and even deleting Pods.Warning: This deployment is not suitable for production use cases, as it uses single instance WordPress and MySQL Pods. Consider using WordPress Helm Chart to deploy WordPress in production.Note: The files provided in this tutorial are using GA Deployment APIs and are specific to kubernetes version 1.9 and later. If you wish to use this tutorial with an earlier version of Kubernetes, please update the API version appropriately, or reference earlier versions of this tutorial.

Objectives

  • Create PersistentVolumeClaims and PersistentVolumes
  • Create a kustomization.yaml with
    • a Secret generator
    • MySQL resource configs
    • WordPress resource configs
  • Apply the kustomization directory by kubectl apply -k ./
  • Clean up

Before you begin

You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using Minikube, or you can use one of these Kubernetes playgrounds:

To check the version, enter kubectl version. The example shown on this page works with kubectl 1.14 and above.

Download the following configuration files:

  1. mysql-deployment.yaml
  2. wordpress-deployment.yaml

Create PersistentVolumeClaims and PersistentVolumes

MySQL and WordPress each require a PersistentVolume to store data. Their PersistentVolumeClaims will be created at the deployment step.

Many cluster environments have a default StorageClass installed. When a StorageClass is not specified in the PersistentVolumeClaim, the cluster’s default StorageClass is used instead.

When a PersistentVolumeClaim is created, a PersistentVolume is dynamically provisioned based on the StorageClass configuration.Warning: In local clusters, the default StorageClass uses the hostPath provisioner. hostPath volumes are only suitable for development and testing. With hostPath volumes, your data lives in /tmp on the node the Pod is scheduled onto and does not move between nodes. If a Pod dies and gets scheduled to another node in the cluster, or the node is rebooted, the data is lost.Note: If you are bringing up a cluster that needs to use the hostPath provisioner, the --enable-hostpath-provisioner flag must be set in the controller-manager component.Note: If you have a Kubernetes cluster running on Google Kubernetes Engine, please follow this guide.

Create a kustomization.yaml

Add a Secret generator

Secret is an object that stores a piece of sensitive data like a password or key. Since 1.14, kubectl supports the management of Kubernetes objects using a kustomization file. You can create a Secret by generators in kustomization.yaml.

Add a Secret generator in kustomization.yaml from the following command. You will need to replace YOUR_PASSWORD with the password you want to use.

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: mysql-pass
  literals:
  - password=YOUR_PASSWORD
EOF

Add resource configs for MySQL and WordPress

The following manifest describes a single-instance MySQL Deployment. The MySQL container mounts the PersistentVolume at /var/lib/mysql. The MYSQL_ROOT_PASSWORD environment variable sets the database password from the Secret.

application/wordpress/mysql-deployment.yaml 
apiVersion: v1 kind: Service metadata: name: wordpress labels: app: wordpress spec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: wp-pv-claim labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi --- apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:4.8-apache name: wordpress env: - name: WORDPRESS_DB_HOST value: wordpress-mysql - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wp-pv-claim

The following manifest describes a single-instance WordPress Deployment. The WordPress container mounts the PersistentVolume at /var/www/html for website data files. The WORDPRESS_DB_HOST environment variable sets the name of the MySQL Service defined above, and WordPress will access the database by Service. The WORDPRESS_DB_PASSWORD environment variable sets the database password from the Secret kustomize generated.

application/wordpress/wordpress-deployment.yaml 
apiVersion: v1 kind: Service metadata: name: wordpress labels: app: wordpress spec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: wp-pv-claim labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi --- apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:4.8-apache name: wordpress env: - name: WORDPRESS_DB_HOST value: wordpress-mysql - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wp-pv-claim
  1. Download the MySQL deployment configuration file.curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml
  2. Download the WordPress configuration file.curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml
  3. Add them to kustomization.yaml file.
cat <<EOF >>./kustomization.yaml
resources:
  - mysql-deployment.yaml
  - wordpress-deployment.yaml
EOF

Apply and Verify

The kustomization.yaml contains all the resources for deploying a WordPress site and a MySQL database. You can apply the directory by

kubectl apply -k ./

Now you can verify that all objects exist.

  1. Verify that the Secret exists by running the following command:kubectl get secrets The response should be like this:NAME TYPE DATA AGE mysql-pass-c57bb4t7mf Opaque 1 9s
  2. Verify that a PersistentVolume got dynamically provisioned.kubectl get pvc Note: It can take up to a few minutes for the PVs to be provisioned and bound.The response should be like this:NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mysql-pv-claim Bound pvc-8cbd7b2e-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s wp-pv-claim Bound pvc-8cd0df54-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s
  3. Verify that the Pod is running by running the following command:kubectl get pods Note: It can take up to a few minutes for the Pod’s Status to be RUNNING.The response should be like this:NAME READY STATUS RESTARTS AGE wordpress-mysql-1894417608-x5dzt 1/1 Running 0 40s
  4. Verify that the Service is running by running the following command:kubectl get services wordpress The response should be like this:NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE wordpress LoadBalancer 10.0.0.89 <pending> 80:32406/TCP 4m Note: Minikube can only expose Services through NodePort. The EXTERNAL-IP is always pending.
  5. Run the following command to get the IP Address for the WordPress Service:minikube service wordpress --url The response should be like this:http://1.2.3.4:32406
  6. Copy the IP address, and load the page in your browser to view your site.You should see the WordPress set up page similar to the following screenshot.wordpress-init

Warning: Do not leave your WordPress installation on this page. If another user finds it, they can set up a website on your instance and use it to serve malicious content.

Either install WordPress by creating a username and password or delete your instance.

Cleaning up

  1. Run the following command to delete your Secret, Deployments, Services and PersistentVolumeClaims:kubectl delete -k ./

What’s next

Non so niente

.. Socrate diceva non so niente, proprio perché se non so niente problematizzo tutto. La filosofia nasce dalla problematizzazione dell’ovvio: non accettiamo quello che c’è, perché se lo facciamo, ce lo ricorda ancora Platone, diventeremo gregge, pecore. La filosofia nasce come istanza critica, non accettazione dell’ovvio, non rassegnazione a quello che oggi va di moda chiamare sano realismo. Mi rendo conto che realisticamente uno che si iscrive a filosofia compie un gesto folle, però forse se non ci sono questi folli il mondo resta così com’è… Allora la filosofia svolge un ruolo decisamente importante, non perché sia competente di qualcosa, ma semplicemente perché non accetta qualcosa, e questa non accettazione di ciò che c’è non la esprime attraverso revolverate o rivoluzioni, l’esprime attraverso un tentativo di trovare le contraddizioni del presente e dell’esistente, e argomentare possibilità di soluzioni: in pratica, pensare. E il giorno in cui noi abdichiamo al pensiero abbiamo abdicato a tutto.

Umberto Galimberti
Percorsi di emancipazione, democrazia ed etica

Salutini

Sto per andare via da voi miei piccolini e mi avete spezzato il cuoricino così tanto che ho bisogno di scrivere, la mia valvola di sfogo.
Sono questi i momenti in cui vorrei ci fosse un regista, un regista della mia vita capace d’immortalare il momento che spero rimanga sempre impresso nella mia testa

I vostri faccini pieni di lacrime che m’implorano di rimanere, i vostri abbracci, Simone, il più fisico, che con la manina cerca la mia barba, struscia la manina, ci gioca, cerca un contatto, mi bacia facendomi sentire le sue guanciotte lacrimose umide .
Mi tenete li, sul divano e non volete che mi alzi, ogni secondo un’ora.
E’ li che capisci che il tempo è relativo, il vero senso della frase “Un giorno, tre autunni”.
Sento e posso toccare l’amore vero, quello che sai è per sempre.

Come si fa a spiegare, come?

Come si fa a spiegare.

“Mi mancherai” mi hai detto Simone, con quella sua vocina meravigliosa che il tempo ti cambierà a favore di una voce sempre più matura ma sempre più lontana da me. Si cresce.

Vorrei che alcune cose rimanessero congelate nel tempo, vorrei che voi rimaneste sempre così ma non si può ed in fondo è bello così, ci godiamo un momento che rimarrà una perla nel nostro cuore e per mano continuiamo a crescere.

Ora che avete chiuso la porta e siete andati, tocca piangere a me.

Sempre insieme.

What is CI/CD

Our space is about mobile CI/CD but, let’s start with the basics in this article before going into the details of why mobile CI/CD is actually different than other CI/CD flows in our future articles.

Image for post

What is Continuous Integration?

Continuous Integration (CI) is the building process for every code pushed to a repository automatically.

This is of course a very simplified definition and CI contains a number of different components, which are like the building blocks that come together to become a workflow.

A CI workflow may include operations like compiling the app, static code reviews and unit/UI testing with automated tools.

There are a number of different tools for CI/CD, some of the most popular ones are Jenkins, Travis and CircleCI, but mobile CI/CD requires specialization and in our space, we will be talking more about mobile CI/CD tools like Appcircle and Bitrise.


What is Continuous Delivery (or Deployment)?

Continuous Delivery (CD) is the delivery of the application for use with the components like the generation of environment-specific versions, codesigning, version management and deployment to a certain provider.

For mobile apps, the deployment can be done to a testing platform like TestFlight (for end user testing on actual devices) or Appcircle (for actual or virtual device testing).

Similarly, the release version of the app can be deployed to App Store and Google Play for B2C apps or to an enterprise app store for B2E apps.


Finally, what is a CI and CD pipeline?

With all CI/CD components are in place and connected together in an automated manner, they form an application pipeline. You just push your code and the code is processed through the pipeline.

CI is mainly responsible for processing the contents of the pipeline and CD is mainly responsible for directing the contents of the pipeline in the right direction.

In a well-functioning application pipeline, you don’t see the actual contents, it just flows by itself, but you have a full visibility on what is flowing and to where just like an actual pipeline.

Demystifying Containers – Part II: Container Runtimes

This second blog post (and talk) is primary scoped to container runtimes, where we will start with their historic origins before digging deeper into two dedicated projects: runc and CRI-O. We will initially build up a great foundation about how container runtimes work under the hood by starting with the lower level runtime runc. Afterwards, we will utilize the more advanced runtime CRI-O to run Kubernetes native workloads, but without even running Kubernetes at all.

Introduction

In the previous part of this series we discussed Linux Kernel Namespaces and everything around to build up a foundation about containers and their basic isolation techniques. Now we want to dive deeper into answering the question: “How to actually run containers?”. We will do so without being overwhelmed by the details of Kubernetes’ features or security related topics, which will be part of further blog posts and talks.

What is a Container Runtime?

Applications and their required or not required use cases are contentiously discussed topics in the UNIX world. The mainUNIX philosophy propagates minimalism and modular software parts which should fit well together in a complete system. Great examples which follow these philosophical aspects are features like the UNIX pipe or text editors like vim. These tools solve one dedicated task as best as they can and are tremendously successful at it. On the other side, there are projects like systemd or cmake, which do not follow the same approach and implement a richer feature set over time. In the end we have multiple views and opinions about answers to questions like ”How should an initialization system look like?” or ”What should a build system do?”. If these multi-opinionated views mix up with historical events, then answering a simple question might need more explanations than it should.

Now, welcome to the world of containers!

Lots of applications can run containers, whereas every application would have a sightly different opinion about what a container runtime should do and support. For example, systemd is able to run containers via systemd-nspawn, and NixOS has integrated container management as well. Not to mention all the other existing container runtimes like CRI-OKata ContainersFirecrackergVisorcontainerdLXCruncNabla Containers and many more. A lot of them are now part of theCloud Native Computing Foundation (CNCF) and their huge landscape, whereas someone might ask: ”Why do so many container runtimes exist?”.

Per usual for our series of blog posts, we should start from the historical beginning.

A Brief History

After the invention of cgroups back in 2008, a project called Linux Containers (LXC) started to pop-up in the wild, which should revolutionize the container world. LXC combined cgroup and namespace technologies to provide an isolated environment for running applications. You may know that we sometimes live in a parallel world. This means that Google started their own containerization project in 2007 called Let Me Contain That For You (LMCTFY), which works mainly at the same level as LXC does. With LMCTFY, Google tried to provide a stable and API driven configuration without users having to understand the details of cgroups and its internals.

If we now look back into 2013 we see that there was a tool written called Docker, which was built on top of the already existing LXC stack. One invention of Docker was that the user is now able to package containers into images to move them between machines. Docker were the fist ones who tried to make containers a standard software unit, as they state in their ”Standard Container Manifesto”.

Some years later they began to work on libcontainer, a Go native way to spawn and manage containers. LMCTFY was abandoned during that time too, whereas the core concepts and major benefits of LMCTFY were ported into libcontainer and Docker.

We are now back in 2015, where projects like Kubernetes hit version 1.0. A lot of stuff was ongoing during that time: The CNCF was founded as part of the Linux Foundation with the target to promote containers. The Open Container Initiative (OCI)was founded 2015 as well, as an open governance structure around the container ecosystem.

Their main target is to create open industry standards around container formats and runtimes. We were now in a state where containers are used, in terms of their popularity, side by side with classic Virtual Machines (VMs). There was a need for a specification of how containers should run, which resulted in the OCI Runtime Specification. Runtime developers should now be able to have a well-defined API to develop their container runtime. The libcontainer project was donated to the OCI during that time, whereas a new tool called runc was born as part of that. With runc it was now possible to directly interact with libcontainer, interpret the OCI Runtime Specification and run containers from it.

As of today, runc is one of the most popular projects in the container ecosystem and is used in a lot of other projects like containerd (used by Docker), CRI-O and podman. Other projects adopted the OCI Runtime Specification as well. For example Kata Containers makes it possible to build and run secure containers including lightweight virtual machines that feel and perform like containers, but provide stronger workload isolation using hardware virtualization technology as a second layer of defense.

Let’s dig more into the OCI Runtime Specification to get a better understanding about how a container runtime works under the hood.

Running Containers

runc

The OCI Runtime Specification provides information about the configuration, execution environment and overall life cycle of a container. A configuration is mainly a JSON file that contains all necessary information to enable the creation of a container on different target platforms like Linux, Windows or Virtual Machines (VMs).

An example specification can be easily generated with runc:

> runc spec
> cat config.json
{
  "ociVersion": "1.0.0",
  "process": {
    "terminal": true,
    "user": { "uid": 0, "gid": 0 },
    "args": ["sh"],
    "env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "TERM=xterm"
    ],
    "cwd": "/",
    "capabilities": {
      "bounding": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"],
      [...]
    },
    "rlimits": [ { "type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024 } ],
    "noNewPrivileges": true
  },
  "root": { "path": "rootfs", "readonly": true },
  "hostname": "runc",
  "mounts": [
    {
      "destination": "/proc",
      "type": "proc",
      "source": "proc"
    },
    [...]
  ],
  "linux": {
    "resources": { "devices": [ { "allow": false, "access": "rwm" } ] },
    "namespaces": [
      { "type": "pid" },
      { "type": "network" },
      { "type": "ipc" },
      { "type": "uts" },
      { "type": "mount" }
    ],
    "maskedPaths": [
      "/proc/kcore",
      [...]
    ],
    "readonlyPaths": [
      "/proc/asound",
      [...]
    ]
  }
}

This file mainly contains all necessary information for runc to get started with running containers. For example, we have attributes about the running process, the defined environment variables, the user and group IDs, needed mount points and the Linux namespaces to be set up. One thing is still missing to get started running containers: We need an appropriate root file-system (rootfs). We already discovered in the past blog post how to obtain it from an already existing container image:

> skopeo copy docker://opensuse/tumbleweed:latest oci:tumbleweed:latest
[output removed]
> sudo umoci unpack --image tumbleweed:latest bundle
[output removed]

Interestingly, the unpacked container image already includes the Runtime Specification we need to run the bundle:

> sudo chown -R $(id -u) bundle
> cat bundle/config.json
{
  "ociVersion": "1.0.0",
  "process": {
    "terminal": true,
    "user": { "uid": 0, "gid": 0 },
    "args": ["/bin/bash"],
    "env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "TERM=xterm",
      "HOME=/root"
    ],
    "cwd": "/",
    "capabilities": { [...] },
    "rlimits": [...]
  },
  "root": { "path": "rootfs" },
  "hostname": "mrsdalloway",
  "mounts": [...],
  "annotations": {
    "org.opencontainers.image.title": "openSUSE Tumbleweed Base Container",
    "org.opencontainers.image.url": "https://www.opensuse.org/",
    "org.opencontainers.image.vendor": "openSUSE Project",
    "org.opencontainers.image.version": "20190517.6.190",
    [...]
  },
  "linux": {
    "resources": { "devices": [ { "allow": false, "access": "rwm" } ] },
    "namespaces": [
      { "type": "pid" },
      { "type": "network" },
      { "type": "ipc" },
      { "type": "uts" },
      { "type": "mount" }
    ]
  }
}

There are now some annotations included beside the usual fields we already know from running runc spec. These can be used to add arbitrary metadata to the container, which can be utilized by higher level runtimes to add additional information to the specification.

Let’s create a new container from the bundle with runc. Before actually calling out to runc, we have to setup a receiver terminal to be able to interact with the container. For this, we can use the recvtty tool included in the runc repository:

> go get github.com/opencontainers/runc/contrib/cmd/recvtty
> recvtty tty.sock

In another terminal, we now call runc create with specifying the bundle and terminal socket:

> sudo runc create -b bundle --console-socket $(pwd)/tty.sock container

No further output, so what happened now? It seems like we have created a new container in created state:

> sudo runc list
ID          PID         STATUS      BUNDLE      CREATED                          OWNER
container   29772       created     /bundle     2019-05-21T08:35:51.382141418Z   root

The container seems to be not running, but what is running inside?

> sudo runc ps container
UID        PID  PPID  C STIME TTY          TIME CMD
root     29772     1  0 10:35 ?        00:00:00 runc init

The runc init command sets up a fresh environment with all necessary namespaces and launches a new initial process. The main process /bin/bash does not run yet inside the container, but we are still able to execute further processes within the container:

> sudo runc exec -t container echo "Hello, world!"
> Hello, world!

The created state of a container provides a nice environment to setup networking for example. To actually do something within the container, we have to bring it into the running state. This can be done via runc start:

> sudo runc start container

In the terminal where the recvtty process is running, a new bash shell session should now pop up:

mrsdalloway:/ $
mrsdalloway:/ $ ps aux
ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   5156  4504 pts/0    Ss   10:28   0:00 /bin/bash
root        29  0.0  0.0   6528  3372 pts/0    R+   10:32   0:00 ps aux

Nice, the container seems to be running. We can now utilize runc to inspect the container’s state:

> sudo runc list
ID          PID         STATUS      BUNDLE      CREATED                          OWNER
container   4985        running     /bundle     2019-05-20T12:14:14.232015447Z   root
> sudo runc ps container
UID        PID  PPID  C STIME TTY          TIME CMD
root      6521  6511  0 14:25 pts/0    00:00:00 /bin/bash

The runc init process has gone and now only the actual /bin/bash process exists within the container. We can also do some basic life cycle management with the container:

> sudo runc pause container

It should now be impossible to get any output from the running container in the recvtty session. To resume the container, simply call:

> sudo runc resume container

Everything we tried to type before should now pop up in the resumed container terminal. If we need more information about the container, like the CPU and memory usage, then we can retrieve them via the runc events API:

> sudo runc events container
{...}

The output is a bit hard to read, so let’s reformat it and strip some fields:

{
  "type": "stats",
  "id": "container",
  "data": {
    "cpu": {
      "usage": {
        "total": 31442016,
        "percpu": [ 5133429, 5848165, 827530, ... ],
        "kernel": 20000000,
        "user": 0
      },
      "throttling": {}
    },
    "memory": {
      "usage": {
        "limit": 9223372036854771712,
        "usage": 1875968,
        "max": 6500352,
        "failcnt": 0
      },
      "swap": { "limit": 0, "failcnt": 0 },
      "kernel": {
        "limit": 9223372036854771712,
        "usage": 311296,
        "max": 901120,
        "failcnt": 0
      },
      "kernelTCP": { "limit": 9223372036854771712, "failcnt": 0 },
      "raw": {
        "active_anon": 1564672,
        [...]
      }
    },
    "pids": { "current": 1 },
    "blkio": {},
    "hugetlb": { "1GB": { "failcnt": 0 }, "2MB": { "failcnt": 0 } },
    "intel_rdt": {}
  }
}

We can see that we are able to retrieve detailed runtime information about the container.

To stop the container, we simply exit the recvtty session. Afterwards the container can be removed with runc delete:

> sudo runc list
ID          PID         STATUS      BUNDLE      CREATED                         OWNER
container   0           stopped     /bundle     2019-05-21T10:28:32.765888075Z  root
> sudo runc delete container
> sudo runc list
ID          PID         STATUS      BUNDLE      CREATED     OWNER

Containers in the stopped state cannot run again, so they have to be recreated from a fresh state. As already mentioned, the extracted bundle contains the necessary config.json file beside the rootfs, which will be used by runc to setup the container. We could for example modify the initial run command of the container by executing:

> cd bundle
> jq '.process.args = ["echo", "Hello, world!"]' config.json | sponge config.json
> sudo runc run container
> Hello, world!

We have nearly every freedom by editing the rootfs or the config.json. So we could tear down the PID namespace isolation between the container and the host:

> jq '.process.args = ["ps", "a"] | del(.linux.namespaces[0])' config.json | sponge config.json
> sudo runc run container
16583 ?        S+     0:00 sudo runc run container
16584 ?        Sl+    0:00 runc run container
16594 pts/0    Rs+    0:00 ps a
[output truncated]

In the end runc is a pretty low level runtime, whereas improper configuration and usage can lead into serious security concerns. Truly, runc has native support for security enhancements like seccompSecurity-Enhanced Linux (SELinux) andAppArmor but these features should be used by higher level runtimes to ensure correct usage in production. It is also worth mentioning that it is possible to run containers in rootless mode via runc to security harden the deployment even further. We will cover these topics in future blog posts as well, but for now that should suffice on that level.

Another drawback in running containers only with runc would be that we have to manually set up the networking to the host to reach out to the internet or other containers. In order to do that we could use the Runtime Specification Hooks feature to set up a default bridge before actually starting the container.

But why don’t we leave this job to a higher level runtime as well? Let’s go for that and move on.

The Kubernetes Container Runtime Interface (CRI)

Back in 2016, the Kubernetes project announced the implementation of the Container Runtime Interface (CRI), which provides a standard API for container runtimes to work with Kubernetes. This interface enables users to exchange the runtime in a cluster with ease.

How does the API work? At the bottom line of every Kubernetes cluster runs a piece of software called the kubelet, which has the main job of keeping container workloads running and healthy. The kubelet connects to a gRPC server on startup and expects a predefined API there. For example, some service definitions of the API look like this:

// Runtime service defines the public APIs for remote container runtimes
service RuntimeService {
    rpc CreateContainer (...) returns (...) {}
    rpc ListContainers  (...) returns (...) {}
    rpc RemoveContainer (...) returns (...) {}
    rpc StartContainer  (...) returns (...) {}
    rpc StopContainer   (...) returns (...) {}

That seems to be pretty much what we already did with runc, managing the container life cycle. If we look further at the API, we see this:

    rpc ListPodSandbox  (...) returns (...) {}
    rpc RemovePodSandbox(...) returns (...) {}
    rpc RunPodSandbox   (...) returns (...) {}
    rpc StopPodSandbox  (...) returns (...) {}
}

What does “sandbox” mean? Containers should already be some kind of sandbox, right? Yes, but in the Kubernetes worldPods can consist of multiple containers, whereas this abstract hierarchy has to be mapped into a simple list of containers. Because of that, every creation of a Kubernetes Pod starts with the setup of a so called PodSandbox. Every container running inside the Pod is attached to this sandbox, so the containers inside can share common resources, like their network interfaces for example. runc alone does not provide such features out of the box, so we have to use a higher level runtime to achieve our goal.

CRI-O

CRI-O is a higher level container runtime which has been written on purpose to be used with the Kubernetes CRI. The name originates from the combination of the Container Runtime Interface and the Open Container Initiative. Isn’t that simple? CRI-O’s journey started as Kubernetes incubator project back in 2016 under the name Open Container Initiative Daemon (OCID). Version 1.0.0 has been released one year later in 2017 and follows the Kubernetes release cycles from that day on. This means for example, that the Kubernetes version 1.15 can be safely used together with CRI-O 1.15 and so on.

The implementation of CRI-O follows the main UNIX philosophy and tends to be a lightweight alternative to Docker or containerd when it comes to running production-ready workloads inside of Kubernetes. It is not meant to be a developers-facing tool which can be used from the command line. CRI-O has only one major task: Fulfilling the Kubernetes CRI. To achieve that, it utilizes runc for basic container management in the back, whereas the gRPC server provides the API in the front end. Everything in between is done either by CRI-O itself or by core libraries like containers/storage or containers/image. But in the end it doesn’t mean that we cannot play around with it, so let’s give it a try.

I prepared a container image called “crio-playground” to get started with CRI-O in an efficient manner. This image contains all necessary tools, example files and a working CRI-O instance running in the background. To start a privileged container running the crio-playground, simply execute:

> sudo podman run --privileged -h crio-playground -it saschagrunert/crio-playground
crio-playground:~ $

From now on we will use a tool called crictl to interface with CRI-O and its Container Runtime Interface implementation. crictl allows us to use YAML representations of the CRI API requests to send them to CRI-O. For example, we can create a new PodSandbox with the sandbox.yml lying around in the current working directory of the playground:

metadata:
  name: sandbox
  namespace: default
dns_config:
  servers:
    - 8.8.8.8

To create the sandbox in the running crio-playground container, we now execute:

crio-playground:~ $ crictl runp sandbox.yml
5f2b94f74b28c092021ad8eeae4903ada4b1ef306adf5eaa0e985672363d6336

Let’s store the identifier of the sandbox as $POD_ID environment variable for later usage as well:

crio-playground:~ $ export POD_ID=5f2b94f74b28c092021ad8eeae4903ada4b1ef306adf5eaa0e985672363d6336

If we now run crictl pods we can see that we finally have one PodSandbox up and running:

crio-playground:~ $ crictl pods
POD ID              CREATED             STATE               NAME                NAMESPACE           ATTEMPT
5f2b94f74b28c       43 seconds ago      Ready               sandbox             default             0

But what’s inside our sandbox? We surely can examine the sandbox further by using runc:

crio-playground:~ $ runc list
ID                                                                 PID         STATUS      BUNDLE                                                                                                             CREATED                          OWNER
5f2b94f74b28c092021ad8eeae4903ada4b1ef306adf5eaa0e985672363d6336   80          running     /run/containers/storage/vfs-containers/5f2b94f74b28c092021ad8eeae4903ada4b1ef306adf5eaa0e985672363d6336/userdata   2019-05-23T13:43:38.798531426Z   root

The sandbox seems to run in a dedicated bundle under /run/containers.

crio-playground:~ $ runc ps $POD_ID
UID        PID  PPID  C STIME TTY          TIME CMD
root        80    68  0 13:43 ?        00:00:00 /pause

Interestingly, there is only one process running inside the sandbox, called pause. As the source code of pause indicates, the main task of this process is to keep the environment running and react to incoming signals. Before we actually create our workload within that sandbox, we have to pre-pull the image we want to run. A trivial example would be to run a web server, so let’s retrieve a nginx image by calling:

crio-playground:~ $ crictl pull nginx:alpine
Image is up to date for docker.io/library/nginx@sha256:0fd68ec4b64b8dbb2bef1f1a5de9d47b658afd3635dc9c45bf0cbeac46e72101

Now let’s create a very simple container definition in YAML, like we did for the sandbox:

metadata:
  name: container
image:
  image: nginx:alpine

And now, let’s kick off the container. For that we have to provide the hash of the sandbox as well as the YAML definitions of the sandbox and container:

crio-playground:~ $ crictl create $POD_ID container.yml sandbox.yml
b205eb2c6abec3e7ade72e0cea09d827968a4c1089483cab06bdf0f4ee82ff0c

Seems to work! Let’s store the container identifier as $CONTAINER_ID for later reuse as well:

crio-playground:~ $ export CONTAINER_ID=b205eb2c6abec3e7ade72e0cea09d827968a4c1089483cab06bdf0f4ee82ff0c

What would you expect if we now check out the status of our two running containers while keeping the CRI API in mind? Correct, the container should be in the created state:

crio-playground:~ $ runc list
ID                                                                 PID         STATUS      BUNDLE                                                                                                             CREATED                          OWNER
5f2b94f74b28c092021ad8eeae4903ada4b1ef306adf5eaa0e985672363d6336   80          running     /run/containers/storage/vfs-containers/5f2b94f74b28c092021ad8eeae4903ada4b1ef306adf5eaa0e985672363d6336/userdata   2019-05-23T13:43:38.798531426Z   root
b205eb2c6abec3e7ade72e0cea09d827968a4c1089483cab06bdf0f4ee82ff0c   343         created     /run/containers/storage/vfs-containers/b205eb2c6abec3e7ade72e0cea09d827968a4c1089483cab06bdf0f4ee82ff0c/userdata   2019-05-23T14:08:53.701174406Z   root

And, like in our previous runc example, the container waits in runc init:

crio-playground:~ $ runc ps $CONTAINER_ID
UID        PID  PPID  C STIME TTY          TIME CMD
root       343   331  0 14:08 ?        00:00:00 /usr/sbin/runc init

crictl shows the container in created as well:

crio-playground:~ $ crictl ps -a
CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID
b205eb2c6abec       nginx:alpine        13 minutes ago      Created             container           0                   5f2b94f74b28c

Now we have to start the workload to get it into the running state:

crio-playground:~ $ crictl start $CONTAINER_ID
b205eb2c6abec3e7ade72e0cea09d827968a4c1089483cab06bdf0f4ee82ff0c

This should be successful, too. Let’s verify if all processes are running correctly:

crio-playground:~ $ crictl ps
CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID
b205eb2c6abec       nginx:alpine        15 minutes ago      Running             container           0                   5f2b94f74b28c

Inside the container should now run an nginx web server:

crio-playground:~ $ runc ps $CONTAINER_ID
UID        PID  PPID  C STIME TTY          TIME CMD
root       343   331  0 14:08 ?        00:00:00 nginx: master process nginx -g daemon off;
100        466   343  0 14:24 ?        00:00:00 nginx: worker process

But how to reach the web servers content now? We did not expose any ports or other advanced configuration for the container, so it should be fairly isolated from the host. The solution lies down in the container networking. Because we use a bridged network configuration in the crio-playground, we can simply access the containers network address. To get these we can exec into the container and list the network interfaces:

crio-playground:~ $ crictl exec $CONTAINER_ID ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 16:04:8c:44:00:59 brd ff:ff:ff:ff:ff:ff
    inet 172.0.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::1404:8cff:fe44:59/64 scope link
       valid_lft forever preferred_lft forever

And now just query the inet address for eth0:

crio-playground:~ $ curl 172.0.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
[output truncated]

Hooray, it works! We successfully run a Kubernetes workload without running Kubernetes!

The overall Kubernetes story about Network Plugins or the Container Network Interface (CNI) is worth another blog post, but that’s a different story and we stop right here with all the magic.

Conclusion

And that’s a wrap for this part of the blog series about the demystification of containers. We discovered the brief history of container runtimes and had the chance to run containers with the low level runtime runc as well as the higher level runtime CRI-O. I can really recommend to have a closer look at the OCI runtime specification and test different configurations within the crio-playground environment. For sure we will see CRI-O in the future again when we talk about container-related topics like security or networking. Besides that, we will have the chance to explore different tools like podmanbuildah or skopeo, which provide more advanced container management solutions. I really hope you enjoyed the read and will continue following my journey into future parts of this series. Feel free to drop me a line anywhere you can find me on the internet. Stay tuned!

You can find all necessary resources about this series on GitHub.