Toda la documentación de este sitio esta abierta a mejoras y correcciones. En el sidebar derecho tienes todas las opciones para contribuir.
Puede ser que parte del contenido lo haya publicado como entrada del Blog y no lo encuentres aquí reflejado.
Versión imprimible multipagina. Haga click aquí para imprimir.
Toda la documentación de este sitio esta abierta a mejoras y correcciones. En el sidebar derecho tienes todas las opciones para contribuir.
Puede ser que parte del contenido lo haya publicado como entrada del Blog y no lo encuentres aquí reflejado.
Introducir usuario en el grupo docker
sudo usermod -a -G docker [nombre_usuario]
Refrescar grupo sin tener que reiniciar
newgrp docker
Buscar un contenedor para descargar
docker search [nombre_contenedor]
Instalar una imagen
docker pull [nombre_imagen]
Listar imágenes instaladas
docker images
Ver imágenes ejecutándose
docker ps
Iniciar una imagen
docker run [nombre_imagen]
Para acceder al contenedor, además de crearlo, se puede hacer de dos maneras. Una es haciendo referencia al IMAGE ID y otra al repositorio (REPOSITORY) y la etiqueta (TAG).
docker run -i -t b72889fa879c /bin/bash
docker run -i -t ubuntu:14.04 /bin/bash
El usuario también puede ponerle una etiqueta personalizada que haga referencia a una imagen instalada en su sistema.
docker tag b72889fa879c oldlts:latest
Para crear el contenedor y ponerlo en marcha hay que seguir el mismo paso de antes, pero cambiando la referencia por la etiqueta creada por el usuario.
docker run -i -t oldlts:latest /bin/bash
Para iniciar un contenedor en modo demonio
docker run -d [identificador_imagen]
Como ya hemos comentado, cada vez que ejecutamos el comando run estamos creando un contenedor nuevo, por lo que lo recomendable es ejecutarlo tan solo una vez. Luego podemos listar los contenedores disponibles a través del siguiente comando.
docker ps -a
Hay dos maneras de poner en marcha el contenedor a través del mismo comando, pudiéndose utilizar su identificador (CONTAINER ID) o su nombre (NAMES).
docker start ef7e107e0aae
docker start lonely_wing
Si se quiere acceder (attach, que se podría traducir por adjuntar o unir) al contenedor se puede recurrir a una de estas dos opciones.
docker attach ef7e107e0aae
docker attach lonely_wing
Salir del terminal de docker sin apagarlo Control + P & Control + Q
Para detener un contenedor
docker stop ef7e107e0aae
docker stop lonely_wing
Para borrar un contenedor
docker rm ef7e107e0aae
docker rm lonely_wing
Parar todos los contenedores
docker stop $(docker ps -a -q)
Terminal de un contenedor arrancado
docker exec -ti f38197856de0 /bin/bash
Eliminar todos los contenedores
docker rm $(docker ps -a -q)
Eliminar todas las imágenes
docker rmi $(docker images -q)
Realizar commit de una imagen
docker commit -a "[información creador]" -m "[versión del programa]" [identificador_container] [nombre_repositorio:nombre_TAG]
Obtener la ruta del registro de un contenedor
docker inspect --format='{{.LogPath}}' $ID_CONTENEDOR
Docker es un proyecto de código abierto que automatiza el despliegue de aplicaciones dentro de contenedores de software. Comenzó como un proyecto interno dentro de dotCloud, empresa enfocada a una plataforma como un servicio PaaS (Platform as a Service). Fué iniciado por Solomon Hykes con contribuciones de otros ingenieros de la compañia.
Docker fué liberado como código abierto en 2013. El 13 de marzo de 2014, con el lanzamiento de la versión 0.9 se dejó de utilizar LXC como entorno de ejecución por defecto y lo reemplazó con su propia biblioteca, libcontainer, escrito en Go. Para 2015 el proyecto ya tenía más 20.000 estrellas en GitHub y más de 900 colaboradores.
Docker se basa en la ejecución de procesos aislados entre sí y empaquetados en “contenedores” con todas las dependencias necesarias para funcionar.
Esto es posible gracias a dos funcionalidades del kernel de linux que se llaman “namespaces” y “cgroups”.
El soporte de los namescaces o espacios de nombres aísla la vista que tiene una aplicación de su entorno operativo, incluyendo árboles de proceso, red, ID de usuario y sistemas de archivos montados. Por otra parte, los cgroups del kernel proporcionan aislamiento de recursos, incluyendo la CPU, la memoria, el bloque de E/S y de la red.
Como resumen, se lanza un proceso aislado con todas las dependencias necesarias para que funcione.
Hemos hablado de procesos y contenedores, pero esto es solo una pequeña pieza de todos los objetos que conforman Docker a día de hoy.
Estos comandos nos permiten guardar y cargar imágenes de docker. Aunque lo común es que las imágenes se descarguen de un repositorio, en ocasiones puede ser útil guardarlas en un fichero y cargarlas en otro equipo mediante ficheros tar.
docker save -o <nombre_imagen>.tar <nombre_imagen>
docker load -i <nombre_imagen>.tar
También puedes ver un vídeo de youtube sobre este tema:
Los contenedores nos han permitido la facilidad y comodidad de empaquetar nuestras aplicaciones y servicios, y también nos permiten asegurar que se ejecuten de forma segura. Sin embargo, las imágenes se contruyen con muchos componentes de terceros sobre los que no tenemos visibilidad. Para esta labor tenemos diferentes herramientas que nos ayudan a analizar la seguridad de nuestros contenedores.
Es sin duda una de las más desconocidas debido a su reciente implementación en la plataforma de Docker pero, dada su integración nativa y que no es necesario realizar instalaciones adicionales, es una herramienta más que adecuada.
Tiene unas limitaciones de uso mensual pero podemos iniciar sesión con una cuenta gratuita para ampliarlo. Snyk.
Podemos utilizar esta herramienta simplemente escribiendo:
docker scan <imagen>
Otra forma de utilizarla, es con el parámetro “–dependency-tree”, el cuál muestra todo el árbol de dependencias de la images.
docker scan --dependency-tree <imagen>
├─ ca-certificates @ 20200601~deb10u1
│ └─ openssl @ 1.1.1d-0+deb10u3
│ └─ openssl/libssl1.1 @ 1.1.1d-0+deb10u3
├─ curl @ 7.64.0-4+deb10u1
│ └─ curl/libcurl4 @ 7.64.0-4+deb10u1
│ ├─ e2fsprogs/libcom-err2 @ 1.44.5-1+deb10u3
│ ├─ krb5/libgssapi-krb5-2 @ 1.17-3
│ │ ├─ e2fsprogs/libcom-err2 @ 1.44.5-1+deb10u3
│ │ ├─ krb5/libk5crypto3 @ 1.17-3
│ │ │ └─ krb5/libkrb5support0 @ 1.17-3
│ │ ├─ krb5/libkrb5-3 @ 1.17-3
│ │ │ ├─ e2fsprogs/libcom-err2 @ 1.44.5-1+deb10u3
│ │ │ ├─ krb5/libk5crypto3 @ 1.17-3
│ │ │ ├─ krb5/libkrb5support0 @ 1.17-3
│ │ │ └─ openssl/libssl1.1 @ 1.1.1d-0+deb10u3
│ │ └─ krb5/libkrb5support0 @ 1.17-3
│ ├─ libidn2/libidn2-0 @ 2.0.5-1+deb10u1
│ │ └─ libunistring/libunistring2 @ 0.9.10-1
│ ├─ krb5/libk5crypto3 @ 1.17-3
│ ├─ krb5/libkrb5-3 @ 1.17-3
│ ├─ openldap/libldap-2.4-2 @ 2.4.47+dfsg-3+deb10u2
│ │ ├─ gnutls28/libgnutls30 @ 3.6.7-4+deb10u4
│ │ │ ├─ nettle/libhogweed4 @ 3.4.1-1
│ │ │ │ └─ nettle/libnettle6 @ 3.4.1-1
│ │ │ ├─ libidn2/libidn2-0 @ 2.0.5-1+deb10u1
│ │ │ ├─ nettle/libnettle6 @ 3.4.1-1
│ │ │ ├─ p11-kit/libp11-kit0 @ 0.23.15-2
│ │ │ │ └─ libffi/libffi6 @ 3.2.1-9
│ │ │ ├─ libtasn1-6 @ 4.13-3
│ │ │ └─ libunistring/libunistring2 @ 0.9.10-1
│ │ ├─ cyrus-sasl2/libsasl2-2 @ 2.1.27+dfsg-1+deb10u1
│ │ │ └─ cyrus-sasl2/libsasl2-modules-db @ 2.1.27+dfsg-1+deb10u1
│ │ │ └─ db5.3/libdb5.3 @ 5.3.28+dfsg1-0.5
│ │ └─ openldap/libldap-common @ 2.4.47+dfsg-3+deb10u2
│ ├─ nghttp2/libnghttp2-14 @ 1.36.0-2+deb10u1
│ ├─ libpsl/libpsl5 @ 0.20.2-2
│ │ ├─ libidn2/libidn2-0 @ 2.0.5-1+deb10u1
│ │ └─ libunistring/libunistring2 @ 0.9.10-1
│ ├─ rtmpdump/librtmp1 @ 2.4+20151223.gitfa8646d.1-2
│ │ ├─ gnutls28/libgnutls30 @ 3.6.7-4+deb10u4
│ │ ├─ nettle/libhogweed4 @ 3.4.1-1
│ │ └─ nettle/libnettle6 @ 3.4.1-1
│ ├─ libssh2/libssh2-1 @ 1.8.0-2.1
│ │ └─ libgcrypt20 @ 1.8.4-5
│ └─ openssl/libssl1.1 @ 1.1.1d-0+deb10u3
├─ gnupg2/dirmngr @ 2.2.12-1+deb10u1
TODO
La instalación de Kubernetes en un cluster de nodos puede ser un proceso complejo. En esta guía agruparé distintos tutoriales de instalación con distintos motores de contenedores y distintos sistemas operativos.
apt install curl apt-transport-https vim git wget gnupg2 \
software-properties-common apt-transport-https ca-certificates uidmap -y
swapoff -a
sed -i '/swap/s/^\(.*\)$/#\1/g' /etc/fstab # Auto comenta la línea de swap en fstab
modprobe overlay
modprobe br_netfilter
sudo tee /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
cat << EOF | tee /etc/sysctl.d/kubernetes.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system # Aplica la configuración
echo "<IP> <NOMBRE>" >> /etc/hosts
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt update && apt install containerd.io -y
mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
Dentro del fichero /etc/containerd/config.toml
hay que cambiar la línea SystemdCgroup = false
por SystemdCgroup = true
.
systemctl enable containerd
systemctl restart containerd
# Agrergar repositorio de Kubernetes
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list
# Instalar clave pública de Kubernetes
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add
# Instalar paquetes
apt update && apt install -y kubelet kubeadm kubectl
# Especificar versión de Kubernetes, por ejemplo:
#apt install -y kubelet=1.24.1-00 kubeadm=1.24.1-00 kubectl=1.24.1-00
# Bloquear actualizaciones automáticas
apt-mark hold kubelet kubeadm kubectl
# Iniciar kubelet
systemctl enable kubelet
kubeadm init --pod-network-cidr=<rango de IPs para pods> --control-plane-endpoint=<Nombre añañadido en /etc/hosts>:6443
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
wget https://docs.projectcalico.org/manifests/calico.yaml
# Editar el archivo de configuración de Calico descomentando las líneas. Quedando así:
- name: CALICO_IPV4POOL_CIDR
value: "rango de IPs para pods"
Por ejemplo:
# The default IPv4 pool to create on startup if none exists. Pod IPs will be
# chosen from this range. Changing this value after installation will have
# no effect. This should fall within `--cluster-cidr`.
- name: CALICO_IPV4POOL_CIDR
value: "192.168.0.0/16"
# The default IPv4 pool to create on startup if none exists. Pod IPs will be
# chosen from this range. Changing this value after installation will have
# no effect. This should fall within `--cluster-cidr`.
kubectl apply -f calico.yaml
Repeticiones de los pasos 1 a 9 del nodo maestro. Esta vez en el fichero /etc/hosts
tenemos que añadir la IP y el nombre del nodo maestro.
kubeadm join <Nombre del nodo maestro>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
El token se puede obtener con el comando kubeadm token list
lanzado en el nodo maestro. Si hubiera expirado, se puede generar uno nuevo con kubeadm token create
.
El hash se puede obtener con el siguiente comando de openssl. Lo lanzamos en el nodo maestro:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
Los pods se son una unidad de ejecución de contenedores, concretamente la unidad más pequeña con la que se puede trabajar en kubernetes. Estos son los comandos básicos para usar un contenedor en Kubernetes.
Especificaremos el nombre que le queremos asignar a ese pod y la imagen que utilizaremos.
kubectl run <nom_pod> --image=<imagen>
kubectl get pods # Listar todos los pods en el cluster
kubectl get pods -o wide # Listar los pods en una tabla más amplia
kubectl get pods <nom_pod> # Listar el pod especificado
kubectl describe pods <nom_pod> # Describe el pod nginx
kubectl -n anchore get pods <nom_pod> -o yaml # Nos devuelve todo el manifiesto del pod
Al hacer un describe del pod veríamos la siguiente salida:
Name: nginx
Namespace: default
Priority: 0
Node: minikube/192.168.49.2
Start Time: Mon, 20 Dec 2021 20:12:08 +0100
Labels: run=nginx
Annotations: <none>
Status: Running
IP: 172.17.0.3
IPs:
IP: 172.17.0.3
Containers:
nginx:
Container ID: docker://f19cee240b99b737dc71db300dcfe2ad51a1596b35b2861aea274820aa841530
Image: nginx
Image ID: docker-pullable://nginx@sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603
Port: <none>
Host Port: <none>
State: Running
Started: Mon, 20 Dec 2021 20:12:14 +0100
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-58m5c (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-58m5c:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 43s default-scheduler Successfully assigned default/nginx to minikube
Normal Pulling 42s kubelet Pulling image "nginx"
Normal Pulled 37s kubelet Successfully pulled image "nginx" in 4.842081346s
Normal Created 37s kubelet Created container nginx
Normal Started 37s kubelet Started container nginx
kubectl delete pod nginx
No saben restaurarse ni replicarse a si mismos. Necesitan de alguien que gestione estos procesos. Para esto se utilizan otro tipo de elementos: Replicasets
Por debajo de todas las acciones de kubernetes, lo que el motor entiende, son archivos manifiesto que definen el tipo de cada elemento.
Cuando se coge cierta experiencia se dejan de usar comandos para usar manifiestos y poder aplicar varios a la vez, haciendo el proceso menos tedioso.
kubectl get pods <nombre pod> -o <formato>
Existen varios formatos de salida pero los más comunes son yaml (el usado nativamente por kubernetes), json, name, go-template (para customizaciones)… https://kubernetes.io/docs/reference/kubectl/overview/#custom-columns
Creamos la definición de un pod de prueba que escribirá “Hello world” cada hora:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo Hello World!; sleep 3600']
Luego podríamos crear el elemento con el comando:
kubectl apply -f pods.yaml
También podríamos eliminarlo usando el manifiesto con el comando:
kubectl deletec-f pods.yaml
Podemos definir varios contenedores en un pod. En este ejemplo podemos ver el balanceo que hace kubernetes a nivel de red entre los distintos contenedores de un pod.
apiVersion: v1
kind: Pod
metadata:
name: doscont
spec:
containers:
- name: cont1
image: python:3.6-alpine
command: ['sh', '-c', 'echo "cont1 > index.html" && python -m http.server 8082']
- name: cont2
image: python:3.6-alpine
command: ['sh', '-c', 'echo "cont2 > index.html" && python -m http.server 8083']
Podemos usar etiquetas para filtrar recursos en proyectos de cierto tamaño. Por ejemplo, separando frontend de backend:
apiVersion: v1
kind: Pod
metadata:
name: podtest2
labels:
app: front
env: dev
spec:
containers:
- name: cont1
image: nginx:alpine
---
apiVersion: v1
kind: Pod
metadata:
name: podtest3
labels:
app: back
env: dev
spec:
containers:
- name: cont1
image: nginx:alpine
Ahora para filtrar desde el cli podríamos usar el parámetro “-l” para ello de la siguiente manera:
kubectl get pod -l app=front
Podríamos filtrar por cualquier variable que hayamos definido, también “env”:
kubectl get pod -l env=dev
Incluso multiples labels a la vez:
kubectl get pod -l app=front,env=dev
Por debajo de todas las acciones de kubernetes, lo que el motor entiende, son archivos manifiesto que definen el tipo de cada elemento.
Cuando se coge cierta experiencia se dejan de usar comandos para usar manifiestos y poder aplicar varios a la vez, haciendo el proceso menos tedioso.
kubectl get pods <nombre pod> -o <formato>
Existen varios formatos de salida pero los más comunes son yaml (el usado nativamente por kubernetes), json, name, go-template (para customizaciones)… https://kubernetes.io/docs/reference/kubectl/overview/#custom-columns
Creamos la definición de un pod de prueba que escribirá “Hello world” cada hora:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo Hello World!; sleep 3600']
Luego podríamos crear el elemento con el comando:
kubectl apply -f pods.yaml
También podríamos eliminarlo usando el manifiesto con el comando:
kubectl deletec-f pods.yaml
Podemos definir varios contenedores en un pod. En este ejemplo podemos ver el balanceo que hace kubernetes a nivel de red entre los distintos contenedores de un pod.
apiVersion: v1
kind: Pod
metadata:
name: doscont
spec:
containers:
- name: cont1
image: python:3.6-alpine
command: ['sh', '-c', 'echo "cont1 > index.html" && python -m http.server 8082']
- name: cont2
image: python:3.6-alpine
command: ['sh', '-c', 'echo "cont2 > index.html" && python -m http.server 8083']
Podemos usar etiquetas para filtrar recursos en proyectos de cierto tamaño. Por ejemplo, separando frontend de backend:
apiVersion: v1
kind: Pod
metadata:
name: podtest2
labels:
app: front
env: dev
spec:
containers:
- name: cont1
image: nginx:alpine
---
apiVersion: v1
kind: Pod
metadata:
name: podtest3
labels:
app: back
env: dev
spec:
containers:
- name: cont1
image: nginx:alpine
Ahora para filtrar desde el cli podríamos usar el parámetro “-l” para ello de la siguiente manera:
kubectl get pod -l app=front
Podríamos filtrar por cualquier variable que hayamos definido, también “env”:
kubectl get pod -l env=dev
Incluso multiples labels a la vez:
kubectl get pod -l app=front,env=dev
Los deployments son elementos de configuración que permiten la creación de una aplicación de una sola instancia.
El deployment gestiona uno o varios objetos replicaset y estos a su vez gestionan uno o más pods.
Este es un ejemplo de su estructura básica:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
Aplicaría la configuración anterior con el comando:
kubectl apply -f deployment.yaml
Este deployment gestionaría los servicios de replicaset y los contenedores de nginx definidos.
Podríamos consultar el estado del deployment con el comando:
kubectl get deployment nginx-deployment
El cual nos devolvería una salida similar a la siguiente:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3 3 3 2m
También podemos crear un deployment con una sola instancia con el comando:
kubectl create deployment nginx --image=nginx
Supongamos que queremos actualizar el deployment para que gestione una nueva imagen, concretamente, las de nginx basadas en alpine. El yaml de configuración quedaría así:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
Aplicamos los cambios de nuevo con el comando ‘kubectl apply -f deployment.yaml’ y esta vez nos devuelve que se ha configurado, en vez de crearse:
kubectl apply -f deployment.yaml
deployment.apps/deployment-test configured #Salida del comando
Además, kubernetes gestiona las actualizaciones de los deployment para que sean progresivo entre un cambio de versión y el servicio de que dan los pods no se interrumpa.
Se puede consultar la actualización del deployment en tiempo real con el comando:
kubectl rollout status deployment nginx-deployment
Este comando nos devolvería paso a paso la actualización del deployment:
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-deployment" successfully rolled out
Si el despliegue ya ha terminado no se mostrará este proceso de actualización. Aun así, podremos consultarlo en el registro de eventos usando el comando:
kubectl describe deployment nginx-deployment
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 4m27s deployment-controller Scaled up replica set nginx-deployment-59c46f7dff to 3
Normal ScalingReplicaSet 4m8s deployment-controller Scaled up replica set nginx-deployment-5c4d5dcbf5 to 1
Normal ScalingReplicaSet 4m4s deployment-controller Scaled down replica set nginx-deployment-59c46f7dff to 2
Normal ScalingReplicaSet 4m4s deployment-controller Scaled up replica set nginx-deployment-5c4d5dcbf5 to 2
Normal ScalingReplicaSet 4m2s deployment-controller Scaled down replica set nginx-deployment-59c46f7dff to 1
Normal ScalingReplicaSet 4m2s deployment-controller Scaled up replica set nginx-deployment-5c4d5dcbf5 to 3
Normal ScalingReplicaSet 4m deployment-controller Scaled down replica set nginx-deployment-59c46f7dff to 0
Podemos escalar el deployment con el comando:
kubectl scale deployment nginx-deployment --replicas=5
Podemos consultar el historial de un deployment con el comando:
kubectl rollout history deployment nginx-deployment
Por defecto, el historial de un deployment muestra las últimas 10 actualizaciones a menos que modifiquemos el valor ‘revisionHistoryLimit’ en los spec del deployment. Por ejemplo:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
revisionHistoryLimit: 3
replicas: 3
selector:
matchLabels:
app: nginx
...
Es posible hacer un rollback a una versión anterior de un deployment, ya sea porque el último despliegue no funcione o la aplicación tenga errores inesperados. Se podría hacer con el comando:
kubectl rollout undo deployment nginx-deployment
Este comando hace un rollback a la versión anterior del deployment. También podríamos especificarle una versión específica:
kubectl rollout undo deployment nginx-deployment --to-revision=1
Podemos pausar un deployment con el comando:
kubectl rollout pause deployment nginx-deployment
Para reanudar un deployment usaremos el comando:
kubectl rollout resume deployment nginx-deployment
Los servicios en kubernetes son una forma de agrupar pods mediande sus etiquetas o labels y disponer a los usuarios el acceso a los recursos que están asociados a ellos.
Los pods en kubernetes son efímeros y cambiaran frecuentemente, con ellos, tambien sus IPs por lo que los servicios entregan una IP única (también tiene DNS), además de balancear las peticiones entre los pods que están asociados a un servicio.
Partiendo del deployment anterior podemos crear un servicio de la misma forma:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- port: 8080
targetPort: 80
Es importante destacar que el selector del servicio tiene que igual al label del deployment para que este funcione.
Haciendo foco en la declaración del servicio tambien hay que destacar que, la instrucción port
indica el puerto al que va a escuchar el servicio, y targetPort
indica el puerto del pod al que el servicio va a enviar las peticiones.
Podemos consultar el estado del servicio con el comando:
kubectl get service nginx-service
kubectl get svc nginx-service # Podemos abreviar el comando anterior
Podemos describir el servicio con el comando:
kubectl describe svc nginx-service
Esto nos devolvería una salida similar a la siguiente:
Name: nginx-service
Namespace: default
Labels: app=front
Annotations: <none>
Selector: app=front
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.98.234.17
IPs: 10.98.234.17
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 172.17.0.3:80,172.17.0.4:80,172.17.0.5:80
Session Affinity: None
Events: <none>
Uno de los datos más interesantes de la salida anterior en el campo Endpoints
. Este recoge las IPs de los pods con los que esta conectando el servicio para automatizar que el usuario pueda acceder a ellos.
Además, si algún pod nuevo aparece o desaparece el servicio sabría y actualizaría los endpoints.
También podríamos listar todos los endpoints del namespace con el commando:
kubectl get endpoints
Podríamos abreviar el comando anterior (con ep
) y a la vez consultar específicamente el endpoint de un servicio:
kubectl get ep nginx-service
La jerarquía de los servicios es la siguiente:
Es el servicio por defecto en kubernetes, en caso de que no especifiquemos ningún otro. Su función es crear una conexión a los pods sin exponerlos a la red externa.
Si listamos los servicios podemos ver que, el servicio nginx-service que lanzamos antes, tiene la IP del cluster asignada pero la IP externa se queda con el valor none
.
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
nginx-service ClusterIP 10.98.234.17 <none> 38080/TCP 10h
Es un servicio que conecta un puerto de nodo (red externa) a un puerto de uno o más pods.
Si no le especificamos un puerto, el servicio utiliza uno en el rango del 30000 al 32767.
Para crear un servicio de tipo NodePort solo tenemos que indicarlo en el type
dentro del spec
del servicio:
apiVersion: 1
kind: Service
metadata:
name: nginx-service
labels:
app: front
spec:
type: NodePort # Definición del tipo de servicio
selector:
app: front
ports:
- port: 8080
targetPort: 80
Este tipo también crea una IP del cluster, pero en este caso también abre un puerto a la red externa.
Sirve para exponer servicios a través de la red externa. Podría ser utilizado para exponer servicios web, servicios de bases de datos, etc.
Se podría definir un servicio de tipo LoadBalancer con el siguiente comando para un deployment específico:
kubectl expose deployment nginx-deployment --type=LoadBalancer
Podríamos crear un servicio vía kubectl
:
kubectl expose deployment/nginx --port=80 --type=NodePort
Podemos consultar los servicios con los siguientes comandos:
kubectl get svc # Listar todos los servicios
kubectl get svc nginx -o yaml # Listar un servicio concreto
Podemos borrar un endpoint con el comando:
kubectl delete endpoint nginx
kubectl delete ep nginx # Podemos abreviar el comando anterior
El ingress controller
es un servicio que se ejecuta en un pod y que permite observar los objetos endpoint
. Cuando un nuevo objeto es creado, ingress controller
lo detecta y aplica las reglas que tenga definidas para enrutar el tráfico (normalmente HTTP).
En resumen, permite enrutar tráfico desde fuera de un cluster a los servicios del mismo.
Cualquier tecnología que sirviera como proxy inverso se puede utilizar como ingress controller
. Uno de los más comunes es nginx.
Podemos instalar el ingress controller basado en nginx con helm. En esta página tengo la documentación sobre Helm.
Primero añadimos el repositorio de ingress-nginx
y actualizamos:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
Descargamos el chart:
helm fetch ingress-nginx/ingress-nginx --untar
Modificamos el fichero values.yaml
y en la línea que pone kind: Deployment
actualizamos el valor por DaemonSet
quedando así:
## DaemonSet or Deployment
kind: DaemonSet
Instalamos el chart que acabamos de modificar:
helm install myingress .
Ahora ya podemos añadir objetos de tipo ingress
en kubernetes.
Podemos declarar el objeto del manifiesto de kubernetes como en el siguiente ejemplo:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
rules:
- host: <hostname>
http:
paths:
- backend:
service:
name: <nombre>
port:
number: <puerto>
path: /
pathType: ImplementationSpecific
Los principales comando de gestión son:
kubectl get ingress
kubectl delete ingress <nombre>
kubectl edit ingress <nombre>
Los Jobs
en kubernetes son una forma de automatizar tareas en kubernetes. A diferencia de los Pods
, los Jobs
tienen número de ejecuciones definido y un tiempo de ejecución limitado.
Estos recursos se suelen utilizar para tareas de mantenimiento que se ejecutan de forma puntual y recurrente.
Este sería un ejemplo de sintáxis básica de un Job
:
apiVersion: batch/v1
kind: Job
metadata:
name: test-job
spec:
completions: 5 # Número de ejecuciones
template:
spec:
containers:
- name: test
image: busybox
command: ["/bin/sleep"]
args: ["3"]
restartPolicy: Never
El parámetro diferenciador del Jobs
frente a los Pods
es el completions
. Este define el número de ejecuciones que se realizarán y una vez que se alcanza el número de ejecuciones, el Job
se detendrá.
Si vemos el estado de un Job
en kubernetes, podemos ver que está en estado Pending
si no se ha iniciado, Running
si se está ejecutando y Succeeded
si se ha terminado con éxito.
Los namespaces son una forma de agrupar y aislar los recursos de kubernetes. Esto permite que podamos segregar los diferentes recursos de una aplicación ( pod, deployment, service, etc) para establecer unas cuotas recursos, políticas de seguridad y configuraciones específicas.
Por omisión, kubernetes crea un namespace llamado default
que es el namespace por defecto.
Podemos listar los namespaces con el comando:
kubectl get ns
Esto nos muestra nuestro namespace por defecto y los namespaces del sistema de kubernetes (no tocar estos namespaces):
NAME STATUS AGE
default Active 26d
kube-node-lease Active 26d
kube-public Active 26d
kube-system Active 26d
Puede ser interesante listar los namespaces junto con sus `labels``:
kubectl get ns --show-labels
Este comando nos muestra nuestros namespaces de la siguiente forma:
NAME STATUS AGE LABELS
default Active 26d kubernetes.io/metadata.name=default
kube-node-lease Active 26d kubernetes.io/metadata.name=kube-node-lease
kube-public Active 26d kubernetes.io/metadata.name=kube-public
kube-system Active 26d kubernetes.io/metadata.name=kube-system
Podemos crear un namespace simplemente con el comando:
kubectl create ns <nombre-namespace>
Aún así, también también lo podemos crear con un fichero de configuración (este en formato json) para dejarlo definido como código:
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"name": "development",
"labels": {
"name": "development"
}
}
}
Para utilizar este json de configuración podemos utilizar el comando:
kubectl create -f <fichero-json>
Podemos borrar un namespace con el comando:
kubectl delete ns <nombre-namespace>
Podríamos ejecutar comandos en cualquier namespace añadiendo el parámentro --namespace
o -n
a cualquier comando, por ejemplo:
kubectl get pods --namespace development
El proceso anterior sería más farragoso, excepto que queramos lanzar un comando puntual en un namespace concreto, es más recomendable utilizar la configuración de contexto de kubectl:
kubectl config set-context <nombre-context> --namespace=<nombre-namespace>
Así estaríamos asociando un namespace a un contexto.
Los contextos en kubernetes permiten definir a nuestro cliente diferentes entornos a los que conectarse. Estos entornos puedes ser namespaces o clusters diferentes.
Podemos ver nuestra configuración con el comando:
kubectl config view
Esto nos mostrará nuestra configuración de organizada de la siguiente manera:
apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
name: development
- cluster:
name: scratch
users:
- name: developer
- name: experimenter
contexts:
- context:
name: dev-frontend
user: developer
cluster: development
- context:
cluster: scratch
namespace: develop
user: experimenter
name: scratch-frontend
Podemos distinguir tres elementros de la configuración:
Esta organización nos permite definir clusters y usuarios individualmente y luego ir asociándolos en contexto concretos.
Este fichero de configuración se suele alojar en el directorio ~/.kube/config
. Podemos añadir una nueva configuración editando el fichero o usando el comando:
kubectl config set-context <nombre del contexto> --namespace=<nombre namespace OPCIONAL> \
--cluster=<nombre del cluster> \
--user=<usuario>
Se puede definir un usuario con el comando en nuestro fichero de configuración con el siguiente comando:
kubectl config set-credentials <nombre-usuario> --client-certificate=<certificado> --client-key=<clave>
También podemos definir por comandos clusters:
kubectl config set-cluster <nombre-cluster> --server=<url del cluster> --certificate-authority=<certificado-autoridad>
Kubernetes nos permite compartir información y configuraciones entre el cluster y los distintos recursos de kubernetes.
Los secretos en kubernetes son una forma de almacenar información sensible. Estos se almacenan en una base de datos de forma privada y pueden consumir por otros recursos de kubernetes.
Podemos obtener, crear o eliminar secretos en kubernetes con los siguientes comandos.
Listar secretos:
kubectl get secrets
Crear secretos:
# Create a new secret named my-secret with keys for each file in folder bar
kubectl create secret generic my-secret --from-file=path/to/bar
# Create a new secret named my-secret with specified keys instead of names on disk
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-file=ssh-publickey=path/to/id_rsa.pub
# Create a new secret named my-secret with key1=supersecret and key2=topsecret
kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
# Create a new secret named my-secret using a combination of a file and a literal
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-literal=passphrase=topsecret
# Create a new secret named my-secret from an env file
kubectl create secret generic my-secret --from-env-file=path/to/bar.env
Borrar un secreto:
kubectl delete secret <nombre>
Un secreto se puede usar en un pod. Podríamos pasarlo como una variable de entorno como en el siguiente ejemplo:
...
spec:
containers:
- image: mysql:5.5
name: dbpod
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql
key: password
También podríamos montarlo como un volumen en su manifest. Este Requeriría el path donde estamos montando un fichero con el contenido del secreto. Mounting Secrets as Volumes You can also mount secrets as files using a volume definition in a pod manifest. The mount path will contain a file whose name will be the key of the secret created with the kubectl create secret step earlier.
...
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /mysqlsecret
name: mysqlsecret
name: busy
volumes:
- name: mysqlsecret
secret:
secretName: mmysql
Dado el caracter efímero de un pod en kubernetes necesitamos algún método para compartir ficheros o información entre los contenedores dentro de un pod.
Para crear un config map usamos el siguiente comando:
kubectl create configmap <nombre> \
--from-literal=text=<texto> \
--from-file=<fichero> \
--from-file=<directorio>
Este nos permite importar información ya sea text en plano que introduzcamos en el comando (--from-literal=text=
), el contenido de un fichero (--from-file=
) o el contenido de un directorio completo (--from-file=
).
Podemos consultar el contenido de un configmap con el siguiente comando:
kubectl get configmap <nombre>
Aunque este solo nos mostrará el total de datos y su edad. Podemos obtener el contenido completo especificando la salida en formato yaml:
kubectl get configmap <nombre> -o yaml
Podríamos declararlo como un yaml para facilitar el almacenamiento de la configuración como código.
apiVersion: v1
kind: ConfigMap
metadata:
name: cars
namespace: default
data:
car.make: Opel
car.model: Astra
car.trim: OPC
Para guardar el configmap en el cluster podemos usar el comando:
kubectl create -f <configmap>.yaml
Podemos configurar el contenido de un configmap en un pod como variable de entorno así:
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: nginx
image: nginx
env:
- name: <nombre de la variable de entorno>
valueFrom:
configMapKeyRef:
name: <nombre_configmap>
key: <clave a usar>
Esto nos permitiría importar una única clave del configmap.
También podríamos importar todo el contenido del configmap así:
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: nginx
image: nginx
envFrom:
- configMapRef:
name: <nombre_configmap>
Cambiaríamos el configMapKeyRef
por configMapRef
y env
por envFrom
. Por último, borraríamos key
y valueFrom
datos que ya no tendríamos que especificar.
Podemos montar un configmap como un volumen en un pod. Esta sería una configuración de ejemplO:
apiVersion: v1
kind: Pod
metadata:
name: shell-demo
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: car-vol
mountPath: /etc/cars
volumes:
- name: car-vol
configMap:
name: cars
Podemos elimitar este objeto de kubernetes con el comando:
kubectl delete
En kubernetes existe la posibilidad de crear volumenes para persistir los datos de los pods. Estos se agrupan en dos objetos:
Esta asiganación de espacio se realiza a dos niveles para reservar espacio para el cluster por un lado (PV) y luego se utilizan los objetos (PVC) para repartir ese espacio entre diferentes proyectos (namespaces) u objetos.
Estos permiten múltiples configuraciones en función del tipo de cluster. En entornos de nube lo normal suele ser usar almacenamiento nativo del proveedor. En este ejemplo lo haremos utilizando un volumen NFS totalmente válido para infraestructura no gestionada por un proveedor cloud.
En esta entrada explico como trabajar con NFS (solo es necesaria la parte de servidor). NFS en Linux
Una vez que tenemos configurado el volumen NFS podemos configurarlo como volumen persistente en kubernetes con una configuración como la siguiente:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pvvol
spec:
capacity:
storage: 40Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /ruta/carpeta/nfs
server: <host> #Puede ser un disco local o remoto
readOnly: false
Los claim sirve para hacer peticiones de espacio al cluster para que pueda ser consumido por un pod.
Podemos consular los PVC existentes con el comando:
kubectl get pvc
Para crear un objeto PVC podemos usar un fichero de configuración como el siguiente:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-test
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 200Mi
Aplicaríamos la configuración anterior con el comando create
:
kubectl create -f config_anterior.yaml
Ya tendríamos nuestro PVC. Este ahora podría ser consumido por un pod. Lo veremos en el siguiente punto.
Podríamos hacer que cualquier pod tuviera acceso a este PVC o volumen con una configuración como la siguiente:
...
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
volumeMounts:
- name: nfs-vol
mountPath: /opt
ports:
- containerPort: 80
protocol: TCP
resources: {}
volumes: # Concretamente todo lo del grupo volumes
- name: nfs-vol
persistentVolumeClaim:
claimName: pvc-test # Importante usar el mismo nombre que la declaración del PVC
...
Una vez terminada la configuración del pod, aplicamos el yaml con el comando create:
kubectl create -f nfs-pod.yaml
Si hacemos un describe del pod podemos ver el montaje de este volumen:
...
Volumes:
nfs-vol:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: pvc-test
ReadOnly: false
...
El objeto encargado de crear cuotas de recursos es ResourceQuota
. Este nos permite limitar el tamaño y número de PVC
TODO
La función de scheduling es la que decide qué podemos ejecutar en un nodo. Por ejemplo, podemos decidir en que nodo se ejecuta un pod en base a varios criterios.
Se pueden utilizar diferentes opciones y etiquetas para esto:
Permiten definir los criterios de scheduling para un clúster, este establece un conjunto de reglas que se aplican a los nodos.
Los labels son una forma de identificar un nodo. Nos permiten agrupar nodos por un criterio, por ejemplo, podemos agrupar los nodos por país o por tipo de hardware.
Para ver la información de un nodo, podemos usar los siguientes comando:
kubectl describe node # Para ver el nodo actual
kubectl describe node <nombre_nodo> # Para ver el nodo especificado
kubectl describe nodes # Para ver todos los nodos
Podríamos ver concretamente los labels de un nodo:
kubectl describe nodes | grep -A5 -i label
Esto mejora la organización de los nodos y nos permite filtrar por un criterio determinado. Además, y donde más potencial obtiene esta característica, podemos configurar los pods con estos labels para que se ejecuten en un nodo determinado.
Podemos especificar el nodo en el que queremos que se ejecute un pod con el parámetro nodeSelector
. Por ejemplo::
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: busybox
command:
- sleep
- "3600"
nodeSelector:
kubernetes.io/hostname: my-node #Para especificar un nodo concreto
Para añadir un label a un nodo, podemos usar el siguiente comando:
kubectl label nodes <nombre_nodo> <nombre_label>=<valor_label>
Para eliminar un label de un nodo, podemos usar el siguiente comando:
kubectl label nodes <nombre_nodo> <nombre_label>-
Siguiendo el ejemplo anterior, podemos añadir un label a un nodo y desplegar un pod en ese nodo.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: busybox
command:
- sleep
- "3600"
nodeSelector:
<label-personalizado>: <valor_label> # Para especificar un nodo concreto
Los taints
nos permiten especificar una restricción a un nodo para controlar donde los pods se están ejecutando y donde tienen permisos de hacerlo.
Existen tres tipos de taints:
NoSchedule
: No permite que se ejecuten pods en el nodo.PreferNoSchedule
: Intenta no ejecutar pods en el nodo.NoExecute
: No permite que se ejecuten pods en el nodo y evita que los pods que ya se estén ejecutando en el nodo se muevan a otro nodo.Los logs son una de las herramientas más importantes para entender el comportamiento de una aplicación. Esta información nos permite entender qué está pasando en el sistema y nos ayuda a depurar errores. En este apartado vamos a ver cómo consumir los logs de los contenedores que se ejecutan en Kubernetes y como integrarlos con otras herramientas de monitorización.
Para los cluster de kubernetes basados en systemd
podemos ver los logs de cada nodo usando el comando journalctl
:
journalctl -u kubelet |less
La mayoría de procesos de docker, actualmente, se ejecutan en contenedores. Para encontrar los ficheros de logs del kube-apiserver
podemos usar el siguiente comando:
sudo find / -name "*apiserver*log"
Luego podemos usar el comando less
para ver el contenido del fichero:
sudo less /var/log/containers/kube-apiserver-k8s-master-1_kube-system_kube-apiserver-1.log # Usa las rutas obtenidas en el comando anterior
Otras rutas donde podemos encontrar logs en función del tipo de nodo son:
/var/log/kube-apiserver.log # Api server
/var/log/kube-scheduler.log # Scheduler
/var/log/kube-controller-manager.log # Controller manager
/var/log/containers # Logs de los contenedores
/var/log/pods/ # Logs de los pods
/var/log/kubelet.log # Logs del kubelet
/var/log/kube-proxy.log # Logs del kube-proxy
Documentación oficial de kubernetes:
Podemos acceder a los logs de un pod usando el comando kubectl logs
:
kubectl -n <namespace> logs <pod> #El comando namespace es opcional, si no se especifica se usa el namespace por defecto
En este apartado vamos a ver cómo añadir herramientas de monitorización y métricas a nuestro cluster de kubernetes. Lo primero será instalar “metrics-server” en nuestro cluster. Para ello vamos a usar el siguiente comando:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
Para entornos de prueba o desarrollo, podemos permitir TLS inseguro. Su certificado es x509 auto firmado y no será válido. Podemos utilizar la flag --kubelet-insecure-tls
para permitir TLS inseguro. NO RECOMENDADO PARA ENTORNOS DE PRODUCCIÓN.
kubectl -n kube-system edit deployment metrics-server
Añadimos la siguiente línea en la sección containers
:
.....
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-insecure-tls # Añadimos esta línea
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
image: k8s.gcr.io/metrics-server/metrics-server:v0.3.6
A partir de aquí, ya podrímos consultar las métricas de nuestros pods usando el comando kubectl top
:
kubectl top pod --all-namespaces # Muestra las métricas de todos los pods
kubectl top pod -n <namespace> # Muestra las métricas de los pods de un namespace
kubectl top node # Muestra las métricas de los nodos
Para instalar el dashboard de kubernetes, podemos usar el siguiente comando (Si no tienes instalado helm te recomiendo que visites su web para instalarlo):
helm repo add k8s-dashboard https://kubernetes.github.io/dashboard # Añadimos el repositorio a helm
helm install <nombre que le quieras dar al despliegue> k8s-dashboard/kubernetes-dashboard
La salida de helm nos dará las instrucción para acceder al dashboard. Ejecuta los comandos indicados, deberían ser similares a los siguientes:
NAME: kube-dashboard
LAST DEPLOYED: Sat Oct 22 16:04:19 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
*********************************************************************************
*** PLEASE BE PATIENT: kubernetes-dashboard may take a few minutes to install ***
*********************************************************************************
Get the Kubernetes Dashboard URL by running:
export POD_NAME=$(kubectl get pods -n default -l "app.kubernetes.io/name=kubernetes-dashboard,app.kubernetes.io/instance=kube-dashboard" -o jsonpath="{.items[0].metadata.name}")
echo https://127.0.0.1:8443/
kubectl -n default port-forward $POD_NAME 8443:8443
Si quisiéramos acceder al dashboard desde fuera del cluster podríamos usar el parámetro --address
, añadido al comando anterior, para indicar la dirección IP desde la que queremos acceder:
kubectl -n default port-forward --address 0.0.0.0 $POD_NAME 8443:8443 # Así cualuier usuario de la red podrá acceder al dashboard, ojo si no es lo que queremos
Durante la instalación, se crea un usuario de servicio para acceder al dashboard. Este no tiene privilegios por lo que no podremos realizar ciertas acciones desde el dashboard. Podemos asignarle privilegios de administrador usando el siguiente comando:
kubectl create clusterrolebinding dashaccess --clusterrole=cluster-admin --serviceaccount=default:<nombre del usuario>
¡OJO! El nombre de usuario depende del nombre que le hayamos dado al despliegue al dashboard. Ante la duda podemos consultar el nombre de nuestro usuario de servicio usando el siguiente comando:
kubectl get serviceaccounts
Nuestro usuario debería ser algo similar a <nombre del despliegue>-kubernetes-dashboard
.
Si accedemos al dashboard, podemos autenticarnos mediante el token de este usuario de servicio ( también podríamos usar el kubeconfig del administrador del cluster). Para obtener el token, ejecutamos el siguiente comando:
kubectl describe secrets dashboard-kubernetes-dashboard-token-<TAB> # TAB para autocompletar el nombre completo del secret
La definición de recursos personalizados o (CRD, Custom Resource Definition) es una de las características más potentes de Kubernetes. Nos permite extender la funcionalidad de Kubernetes añadiendo nuevos tipos de recursos. Estos recursos pueden ser usados por los desarrolladores para definir sus propias abstracciones de alto nivel.
Podemos consultar los recursos personalizados usando el comando kubectl get
:
kubectl get crd --all-namespaces
Es muy normal que aparecen recursos personalizados que no hemos definido nosotros. Estos recursos son definidos por otros componentes de Kubernetes. Por ejemplo, el componente metrics-server
define el recurso pods.metrics.k8s.io
. O por ejemplo, los componentes de calico
definen el recurso networkpolicies.crd.projectcalico.org
.
Podemos obtener detalles de un recurso personalizado usando el comando kubectl describe
:
kubectl describe crd <nombre del recurso>
Para definir un recurso personalizado, debemos crear un fichero YAML con la definición del recurso. Por ejemplo, para definir un recurso personalizado de ejemplo:
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: crontab.example.com
spec:
group: example.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
Podemos crear el recurso personalizado usando el comando kubectl create
:
kubectl create -f crontab.yaml
Para crear un objeto de un recurso personalizado, debemos crear un fichero YAML con la definición del objeto. Por ejemplo, para crear un objeto de ejemplo:
apiVersion: "example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * * */5"
image: my-awesome-cron-image
A partir de aquí, podemos usar el recurso personalizado como si fuera un recurso nativo de Kubernetes. Por ejemplo, podemos consultar los objetos de un recurso personalizado usando el comando kubectl get
:
kubectl get crontab --all-namespaces # Listar todos los objetos crontab de todos los namespaces
kubectl get crontab -n <namespace> # Listar todos los objetos crontab de un namespace
kubectl get crontab <nombre del objeto> -n <namespace> # Obtener detalles de un objeto crontab
kubectl describe crontab <nombre del objeto> -n <namespace> # Obtener detalles de un objeto crontab
La seguridad en Kubernetes es un tema muy amplio. Podemos agruparlo en varias categorías:
Cada llamada que hacemos al API de kubernetes es autenticada y autorizada. Además, podemos configurar un control de admisión para rechazar llamadas que no cumplan ciertas condiciones.
Este diagrama ilustra este proceso:
La autenticación es el proceso de identificación de un usuario. Kubernetes soporta varios métodos de autenticación. Por ejemplo, podemos usar certificados, tokens, contraseñas, etc.
La autorización es el proceso de determinar si un usuario tiene permisos para realizar una acción. Kubernetes soporta varios métodos de autorización. Por ejemplo, podemos usar roles y permisos, políticas de RBAC, etc.
Para esta sección, consulta la documentación sobre usuarios, cuentas y permisos
RBAC es un método de autorización basado en roles. En este método, definimos roles y permisos. Luego, asignamos los roles a los usuarios. Por ejemplo, podemos definir un rol admin
con permisos de lectura y escritura. Luego, podemos asignar este rol a un usuario admin
.
Para conseguir esta granularidad, se definen operaciones CRUD (Create, Read, Update, Delete) sobre recursos. Por ejemplo, podemos definir permisos para crear, leer, actualizar y borrar pods. Luego, podemos asignar estos permisos a un rol. Por ejemplo, podemos definir un rol admin
con permisos para crear, leer, actualizar y borrar pods. Luego, podemos asignar este rol a un usuario admin
.
Esto nos permite definir roles con permisos muy específicos. Por ejemplo, podemos definir un rol pod-reader
con permisos para leer pods. Luego, podemos asignar este rol a un usuario reader
.
El controlador de admisión es un componente que se ejecuta antes de que se realice una acción en el API de kubernetes. Podemos configurar varios controladores de admisión.
Podemos ver las configuraciones de los controladores de admisión en el fichero /etc/kubernetes/manifests/kube-apiserver.yaml
en el nodo maestro. Por ejemplo:
sudo grep admission /etc/kubernetes/manifests/kube-apiserver.yaml
Los pods y contenedores en kubernetes pueden ejecutarse con un contexto de seguridad. Este contexto de seguridad define los permisos que tiene el contenedor, sus capacidades y limitaciones, etc.
Por ejemplo, podemos configurar un contexto de seguridad para que un contenedor no pueda ejecutar comandos como sudo
o su
. También podemos configurar un contexto de seguridad para que un contenedor no pueda ejecutar comandos como mount
o umount
.
Estos contextos se definen dentro de los spec
de los pods. Por ejemplo, podemos definir un contexto de seguridad para un pod de la siguiente forma:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
Si una de las propiedades del contexto de seguridad se incumple, el contenedor no se ejecutará. Se quedará en un estado de error y en su mensaje de estado se mostrará el motivo del error.
DEPRECADO: Pod Security Policy está deprecado en Kubernetes 1.21 y se eliminará en Kubernetes 1.25. En su lugar, se recomienda usar Admission Controller de Pod Security.
Las Pod Security Policies (PSP) sos permiten definir políticas de seguridad para todos los pods, a diferencia de la aproximación anterior, la cuál requeria definir el contexto de seguridad en cada pod.
Podemos definir una PSP para que los pods no puedan ejecutar comandos como sudo
o su
, no puedan ejecutar ciertos comandos, sean incapaz de montar volúmenes, etc.
Por defecto, los pods en kubernetes pueden comunicarse con cualquier otro pod y todo tipo de tráfico es permitido. Podemos configurar políticas de red para restringir este tráfico.
Cuando aplicamos una política, por defecto, se restringe todo el tráfico de entrada y salida. Luego debemos configurar manualmente las excepciones.
Por ejemplo, podemos configurar una política de red para que un pod solo pueda comunicarse con otros pods que tengan un label app=nginx
. También podemos configurar una política de red para que un pod solo pueda comunicarse con otros pods que tengan un label app=nginx
y que estén en el mismo namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-egress-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
En este ejemplo, restringimos el tráfico entrante a un rango de IPs (con una excepción), en un namespace concreto y en los pods con el label ‘frontend’. También limitamos este tráfico a un puerto concreto.
También restringimos el tráfico saliente a un rango de IPs y a un puerto concreto.
Podemos usar {}
para seleccionar todos los pods y no permitir ningún tráfico. Por ejemplo, para todo el tráfico de entrada:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-egress-policy
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
Si no especificamos el tráfico de salida egress
, no se verá afectado por esta política.
Helm es una herramienta que nos permite gestionar, versionar y desplegar múltiples recursos de kubernetes.
Los componentes en helm se estructuran de la siguiente manera:
├── Chart.yaml
├── README.md
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── pvc.yaml
│ ├── secrets.yaml
│ └── svc.yaml
└── values.yaml
El fichero chart.yaml
contiene los metadatos del chart
, el values
contiene las claves y atributos a modificar y el templates contienen los manifiestos de kubernetes.
Las templates se generan como un recurso nombre de kubernetes solo que plantillando las variables para que el chart sirva a diferentes propósitos y organizaciones. Por ejemplo:
apiVersion: v1
kind: Secret
metadata:
name: {{ template "fullname" . }}
labels:
app: {{ template "fullname" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
type: Opaque
data:
mariadb-root-password: {{ default "" .Values.mariadbRootPassword | b64enc | quote }}
mariadb-password: {{ default "" .Values.mariadbPassword | b64enc | quote }}
Este elemento de tipo secreto esta plantillado para que todos sus campos se recojan del fichero values. Esto nos permite, centralizar todos los valores en un único fichero (values.yaml) y por otra parte, permitir que nuestros elementos se kubernetes sean reutilizables.
Este conjunto de elementos se llama chart
y se pueden interactuar con ellos como repositorios de git.
Por defecto, helm busca charts
dentro de la web de Artifactory Hub.
Podríamos buscar charts con el comando:
helm search hub <nombre del chart> #Buscar repositorios
helm search repo <nomrbre del repositorio> #Buscar dentro del repositorio
También podríamos añadir nuevos repositorios, por ejemplo, el de bitnami:
helm repo add bitnami https://charts.bitnami.com/bitnami
Los repositorios que trae por defecto y los que añadimos nosotros manualmente, pueden actualizarse con el comando:
helm repo update
Si ahora quisieramos buscar charts
solo dentro de este repositorio podríamos hacerlo así:
helm search repo bitnami
Podríamos desplegar un chart
con el comando helm install
pero la mayoría de ellos necesitan una personalización para que funcionen correctamente por lo que primero debemos descargarlos en local para leer su README y modificar los valores pertinentes.
Esto lo podríamos hacer con el comando:
helm fetch <nombre repositorio> --untar
Tras modificar todo lo que nos resultara necesario, podemos lanzar el siguiente comando en la ruta del repositorio:
helm install <nombre del despliegue>
Podríamos desinstalarlo con el comando:
helm uninstall <nombre del despliegue>
También podríamos listar todos los charts desplegados y sus respectivas versiones con el comando:
helm list
Por defecto, Kubernetes no tiene usuarios ni roles. Sin embargo, podemos definir usuarios y roles para controlar el acceso a los recursos de Kubernetes.
Normalmente los usuarios se definen en un sistema de autenticación externo, como LDAP, Active Directory, etc. Kubernetes no tiene un sistema de autenticación propio, pero puede integrarse con sistemas de autenticación externos mediante plugins de autenticación.
Se pueden definir usuarios de forma manual mediante certificados x509.
Estos pasos se tienen que realizar en un control plane
de Kubernetes, el cuál, ejecuta el API y es el encargado de validar los certificados x509.
Primero tendremos que crear un par de claves privada y pública. Para ello, ejecutamos el siguiente comando:
openssl genrsa -out user.key 2048
A continuación, generamos el certificado x509:
openssl req -new -key user.key -out user.csr -subj "/CN=user/O=group"
Finalmente, firmamos el certificado x509 con la clave privada del control plane
:
openssl x509 -req -in user.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out user.crt -days 500
Una vez generado el certificado x509, podemos añadirlo al API de Kubernetes. Para ello, ejecutamos el siguiente comando:
kubectl config set-credentials user --client-certificate=user.crt --client-key=user.key --embed-certs=true
Por defecto, este usuario carecerá de permisos para realizar ninguna acción en el cluster. Para asignarle permisos, tendremos que crear asignarle un rol ( a nivel de namespace) o un clusterrole (a nivel de cluster).
Esta asignación de un rol, se realiza mediante rolesbindings ( a nivel de namespace) o clusterrolebindings (a nivel de rol).
Kubernetes crea una cuenta de servicio por defecto para cada namespace. Esta cuenta de servicio se utiliza para acceder a la API de Kubernetes. Podemos crear cuentas de servicio adicionales para acceder a la API de Kubernetes.
kubectl create serviceaccount <nombre>
Estas cuentas podríamos asocialas a un role
mediante objetos RoleBinding
o ClusterRoleBinding
como se explica en los siguientes pasos.
Podemos definir roles en kubebernetes creando objetos de tile Role
. Por ejemplo:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-admin
rules:
- apiGroups: ["","extensions","apps"] # "" indica el core API group
resources: ["pods"]
verbs: ["get", "watch", "list", "delete", "create", "update", "patch"] # Podríamos usar * para indicar todos los verbos
Estos nos permiten granularizar el acceso a cada uno de los recursos y los permisos específicos que les queremos otorgar.
Crear un rol solo es el primer paso. Para usarlo, tenemos que asignarlo a un usuario o grupo de usuarios. Para ello, podemos usar un objeto de tipo RoleBinding
. Por ejemplo:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jdoe # Nombre del usuario
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role # Este debe ser Role o ClusterRole
name: pod-reader # Este debe ser el nombre del rol que queremos asignar
apiGroup: rbac.authorization.k8s.io
Con esto, creamos la relación entre el rol pod-reader
y el usuario jdoe
.
Podemos consultar los roles y los bindings usando los comandos kubectl get roles
y kubectl get rolebindings
. Estos comandos nos devolverán los roles y los bindings de todos los namespaces. Si queremos consultar los roles y los bindings de un namespace en concreto, podemos usar el flag -n
o --namespace
.
Por ejemplo:
kubectl get roles -n default
kubectl get rolebindings -n default
Estos nos mostrarían una salida similar a la siguiente:
Name: pod-admin
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
deployments [] [] [list get watch create update patch delete]
pods [] [] [list get watch create update patch delete]
replicasets [] [] [list get watch create update patch delete]
deployments.apps [] [] [list get watch create update patch delete]
pods.apps [] [] [list get watch create update patch delete]
replicasets.apps [] [] [list get watch create update patch delete]
deployments.extensions [] [] [list get watch create update patch delete]
pods.extensions [] [] [list get watch create update patch delete]
replicasets.extensions [] [] [list get watch create update patch delete]
En el caso de los bindings, la salida sería similar a la siguiente:
Name: read-pods
Labels: <none>
Annotations: <none>
Role:
Kind: Role
Name: dev-prod
Subjects:
Kind Name Namespace
---- ---- ---------
User jdoe
El mantenimiento de kubernetes es una tarea que se realiza con frecuencia.
Durante los procesos de actualización, por muy estables que sean, siempre es buena idea crear una copia de seguridad de la base de datos del cluster.
sudo grep data-dir /etc/kubernetes/manifests/etcd.yaml
Toda esta parte se realiza ejecutando comandos dentro del contenedor de etcd. Se llama etcd-<nombre nodo>
, aunque podrías listar los pods del sistema para encontrarlo con kubectl -n kube-system get pods
.
kubectl -n kube-system exec -it etcd-<nombre_pod> -- sh -c "ETCDCTL_API=3 \
ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt \
ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt \
ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key \
etcdctl endpoint health"
kubectl -n kube-system exec -it etcd-kube-master -- sh -c "ETCDCTL_API=3 \
ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt \
ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt \
ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key \
etcdctl --endpoints=https://127.0.0.1:2379 member list -w table"
snapshot
:kubectl -n kube-system exec -it etcd-kube-master -- sh -c "ETCDCTL_API=3 \
ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt \
ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt \
ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key \
etcdctl --endpoints=https://127.0.0.1:2379 snapshot save /var/lib/etcd/snapshot.db"
Si hacemos un ls en directorio del paso 1 (normalmente /var/lib/etcd) podremos ver el la base de datos que acabamos de extraer:
sudo ls -l /var/lib/etcd
Lo primero es actualizar la herramienta kubeadm, la cuál, nos ayudará a actualizar el cluster.
sudo apt update
madison
las versiones disponibles con la herramienta madison
:sudo apt-cache madison kubeadm
sudo apt-mark unhold kubeadm
sudo apt install -y kubeadm=1.23.1-00
sudo apt-mark hold kubeadm
sudo kubeadm version
kubectl drain <nombre_nodo> --ignore-daemonsets
kubeadm
nos permite previsualizar los cambios que va a
generar la actualización con el comando plan
:sudo kubeadm upgrade plan
apply
:sudo kubeadm upgrade plan
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet=1.23.1-00 kubectl=1.23.1-00
sudo apt-mark hold kubelet kubectl
kubectl get nodes
nos seguirá mostrando la versión anterior. La actualización se hará
efectiva hasta que reiniciemos los servicios:sudo systemctl daemon-reload
sudo systemctl restart kubelet
kubectl uncordon <nombre_nodo>
Se puede verificar el estado con el comando:
kubectl get nodes
Los contenedores son procesos aislados que, por defecto, ¿se podrían considerar como seguros?. Su enfoque nos dice que sí pero existen muchos casos en los que, principalmente por malas configuraciones, podrían ser vulnerables.
Tecnologías de contenedores como Docker, LXC, LXD, etc.. permiten a los usuarios lanzar un proceso aislado pero, existen multiples funcionalidades, que podrían comprometer la aplicación en mayor o menor medida:
Esta funcionalidad permite montar un volumen en un contenedor. Un volumen puede ser una carpeta o archivo en el sistema de archivos del host o un filesystem aislado que cree docker junto con el contenedor. Los volúmenes se suelen utilizar para dar persistencia a los datos de un contenedor y así evitar cuando se para o se vuelve a desplegar un contenedor los datos ser pierdan.
Cuando montamos como volumen parte del host en un contenedor tenemos que tener en cuenta el usuario que ejecuta el engine de docker y el grupo de permisos y, por otra parte, el usuario que ejecuta el contenedor. Si montamos ficheros del hosts sensibles y los montamos en cualquier contenedor que ejecute el usuario root, este usuario sería capaz de acceder a los ficheros. Es importante que los contenedores no utilicen volúmenes sensibles, montar ficheros o directorios muy específicos y que los contenedores no utilicen el usuario root.
También se podría cambiar el usuario que ejecuta el engine de docker pero acarreo muchos problemas de funcionamiento a día de hoy y no lo recomiendo. Lo ideal es crear un usuario en el host y asignarle la propiedad de los archivos que queremos montar en el contenedor y, a su vez, ejecutar el contenedor con dicho usuario.
Si por ejemplo ejecutamos un contenedor montado un directorio del host (en este caso /etc
) y ejecutamos el contenedor como root, este podrá leerlo y modificarlo sin problemas.
docker run -it -v /etc:/host busybox sh
cat /host/shadow # Comando dentro del contenedor
root:*:18970:0:99999:7:::
...
systemd-timesync:*:18970:0:99999:7:::
systemd-network:*:18970:0:99999:7:::
systemd-resolve:*:18970:0:99999:7:::
strike:<CENSORED>:18986:0:99999:7:::
...
En mi linux tengo un usuario strike que no tiene permiso de root. Vamos a ejecutar este contenedor con este usuario para entender que docker arrastra los permisos de archivos del host a los contenedores.
Primero hago un cat /etc/passwd
para obtener el uid de mi usuario strike:
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
strike:x:1000:1000:,,,:/home/strike:/usr/bin/zsh
...
Sabiendo que el uid de mi usuario strike es 1000, vamos a ejecutar el contenedor con este usuario (especificamos el usuario con el parámetro -u <uid>
):
docker run -it -u 1000 -v /etc/:/host busybox sh
cat /host/shadow # Comando dentro del contenedor> $ docker run -it -u 1000 -v /etc/:/host busybox
cat: cant open host/shadow: Permission denied
/ $
Es importante entender esto para no comprometer la seguridad de los archivos del host. Por eso hay que evitar utilizar el usuario root en los contenedores y, por otra parte, evitar montar ficheros sensibles.
Este modo de ejecución permite a un contenedor acceder a ciertos recursos que, por defecto, estan restringidos. Este modo se activa con la opción --privileged
en el comando run de un contenedor.
Esto permitiría acceder al hardware del host y los recursos de red. Podría montar dispositivos como USB, interfaces de red.. etc.
Dicho esto, la forma más sencilla de escalar privilegios es montando el disco del host y buscando secretos u otros accesos:
# Dentro del contenedor privilegiado suponiendo que el disco del hosts se llama /dev/sda
mkdir -p /mnt/hola
mount /dev/sda1 /mnt/hola
Hay más formas pero he documentado la más sencilla e interesante. En esta referencia podéis encontrar más formas: Documentación de Hacktricks
Imaginaros ahora que hemos accedido a un hosts del que no somos root pero tiene docker instalado y nuestro usuario tiene permisos para ejecutar docker. Podríamos ejecutar un contenedor en modo privilegiado para acceder a los recursos del host y conseguir escalar.
docker run -it -v /:/host/ debian chroot /host/ bash
Aunque no es muy frecuente, aparecen vulnerabilidades en las tecnologías, ya sea en docker, el kernel de linux, etc. que puede permitir que un host sea vulnerado.
Como siempre, la recomendación es tener actualizado el kernel de linux a la última versión estable, así como también el engine de docker o la tecnología de contenedores que estés utilizando.
El objetivo de crear una imagen de contenedor es paquetizar tu software para que esté listo para arrancar al instante, eso sí, siempre requiere una configuración en la mayoría de los casos. Si es una base de datos, necesitará definir usuarios y contraseñas, si es una página web, necesitará definir una configuración de servidor, etc.
Meter esos secretos en la imagen sería un fallo de seguridad y además rompería la versatilidad de coger una imagen que pueda funcionar en diferentes casos. Para configurar un contenedor, lo más común, es añadir variables de entono a la imagen en tiempo de ejecución.
Por ejemplo, para configurar un servidor de base de datos de mariadb y que funcione en un contenedor tenemos que definir al menos la contraseña del usuario root:
docker run --name some-mariadb -e MARIADB_ROOT_PASSWORD=contraseña -d mariadb:latest
Muchas aplicaciones no gestionan esto correctamente, es decir, no limpian las variables de entorno que datos sensibles una vez que cargan los secretos en memoria.
Por eso, uno de los primeros pasos de un pentester es consultar el environment
del contenedor:
# Simplemente entrando al terminal del contenedor y ejecutando el comando env dentro del contenedor
> $ docker exec -it some-mariadb /bin/bash
root@5f3f1ce5b7e1: env
HOSTNAME=5f3f1ce5b7e1
PWD=/
HOME=/root
MARIADB_VERSION=1:10.7.3+maria~focal
GOSU_VERSION=1.14
TERM=xterm
MARIADB_MAJOR=10.7
SHLVL=1
MARIADB_ROOT_PASSWORD=contraseña
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env
Podríamos ver que el credencial sigue ahí una vez arrancado el contenedor.
También podríamos ver las variables de entorno desde fuera con el siguiente comando:
docker container inspect --format '{{.Config.Env}}' <nombre contenedor>
Cuando utilizamos diferentes comandos de docker, como por ejemplo docker run, lo que hace el cliente de docker es comunicarse con el engine mediante un socket.
En algunos escenarios en los que se necesita ejecutar comandos de docker dentro de un contenedor, por ejemplo, un orquestador de servicios montado sobre docker que necesite levantar otros contenedores a su vez.
Ejemplo de una herramienta
Jenkins
que orquesta el despliegue de contenedores. A su vez, esta herramienta también está dentro de un contenedor y tiene el socket de docker montado. Por último, tanto el contenedor del front como el del jenkins están expuestos a internet.
Si este orquestador es vulnerado por un atacante, teniendo acceso al socket del docker engine (que recordemos que se ejecuta con el usuario root), podría montar el sistema de archivos del host con permisos de root fácilmente.
Por ejemplo, para docker:
docker run -it -v /:/host/ debian:11 chroot /host/ bash
Docker por defecto crea una red donde ejecuta todos estos contenedores. Si no especificamos nada, todos los contenedores se ejecutan en la misma red. Esto puede permitir que se comprometa las seguridad de otros contenedores de la red.
Supongamos el escenario anterior del jenkins con el socket de docker montado. Imaginaros que este caso pudiésemos vulnerar el back de la aplicación. Este no tendría acceso directamente al socket de docker pero podríamos intentar pivotar a otros contenedores de la red.
Para solventar esto, en el momento de la creación de un contenedor, podemos especificar una red diferente. Por ejemplo, para aislar la aplicación web completamente del jenkins:
# Primero creamos la red
docker network create <nombre de la red>
# Creamos el front y el back de la aplicación y los añadimos a la nueva red
docker run -d --name front --network <nombre de la red> <imagen del front>
docker run -d --name back --network <nombre de la red> <imagen del back>
Así evitaríamos que aunque una aplicación sea vulnerada no afecte al resto de contenedor y servicios que estén desplegados en el mismo host.
Esta herramienta permite enumerar y escalar privilegios en contenedores. Está escrita puramente en sh
sin ninguna dependencia pero, para aprovechar todas las funcionalidades, usa herramientas como curl
, nmap
, nslookup
y dig
si estan disponibles.
Este es su repositorio de github: https://github.com/stealthcopter/deepce
La descarga de la aplicación se puede hacer:
# Con wget
wget https://github.com/stealthcopter/deepce/raw/main/deepce.sh
# Con curl
curl -sL https://github.com/stealthcopter/deepce/raw/main/deepce.sh -o deepce.sh
Una vez descargado, le asignamos permisos de ejecución y lo ejecutamos:
chmod +x deepce.sh
./deepce.sh
En la parte de reconocimiento o fingerprinting, se busca obtener información de los objetivos, para ello se utilizan herramientas como nmap, que nos permite obtener información de los puertos abiertos, servicios, versiones, etc.
La mayoría de sistemas operativos enfocados en pentesting tienen instalado nmap, por lo que no es necesario instalarlo. Por si no estuvieras utilizando kalilinux, parrot, blackarch… etc, podrías instalarlo de la siguiente manera.
En sistemas basados en Debian, podrías instalarlo de la siguiente manera.
sudo apt install nmap
En el caso de sistemas basados en Arch, podrías instalarlo de la siguiente manera.
sudo pacman -S nmap
Para sistemas con paquetes rpm, podrías instalarlo de la siguiente manera.
sudo dnf install nmap
Y por último, para sistemas con paquetería yum, podrías instalarlo de la siguiente manera.
sudo yum install nmap
Los primero que haremos será descubrir los hosts que están en la red, para ello utilizaremos el comando nmap
, con el parámetro -sn
, que nos permite realizar un escaneo ping, para descubrir los hosts que están en la red.
nmap -sn <rango de ips>
Los ragos en de IPs en nmap, se pueden definir de la siguiente manera.
nmap -sn 192.168.1.0-254 # Rango de IPs específico
nmap -sn 192.168.0-32.0-254 # Múltiples rangos específicos
nmap -sn 192.168.1.0/24 # Rango de IPs con máscara de red
RECUERDA: Un escaneo ping utiliza el protocolo ICMP, el cual es bloqueado por muchos cortafuegos, por lo que no siempre funcionará. Como alternativa, podrías utilizar el comando arp-scan
, que utiliza el protocolo ARP, el cual no es bloqueado por los cortafuegos.
arp-scan -l # Escaneo de hosts en la red
Nmap tiene múltiples opciones para realizar escaneos de puertos usando diferentes técnicas y combinaciones de flags en los paquetes TCP y UDP. En esta sección, se explicarán los escaneos más comunes.
El escaneo TCP SYN es el más común y el más rápido. Este escaneo envía paquetes TCP SYN a los puertos especificados y espera una respuesta. Si el puerto está abierto, el objetivo responde con un paquete TCP SYN/ACK. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sS <ip>
El escaneo TCP Connect es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes TCP SYN/ACK. Si el puerto está abierto, el objetivo responde con un paquete TCP ACK. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sT <ip>
El escaneo TCP ACK es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes TCP ACK. Si el puerto está abierto, el objetivo responde con un paquete TCP RST. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sA <ip>
El escaneo TCP Window es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes TCP SYN/ACK con el flag de ventana en 0. Si el puerto está abierto, el objetivo responde con un paquete TCP RST. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sW <ip>
El escaneo TCP Null es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes TCP con todos los flags en 0. Si el puerto está abierto, el objetivo responde con un paquete TCP RST. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sN <ip>
El escaneo TCP Fin es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes TCP FIN. Si el puerto está abierto, el objetivo responde con un paquete TCP RST. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sF <ip>
El escaneo TCP Xmas es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes TCP con los flags de urgente, push y fin en 1. Si el puerto está abierto, el objetivo responde con un paquete TCP RST. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sX <ip>
El escaneo TCP Idle es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes TCP con el flag de urgente en 1. Si el puerto está abierto, el objetivo responde con un paquete TCP RST. Si el puerto está cerrado, el objetivo responde con un paquete TCP RST. Si el puerto está filtrado, el objetivo no responde.
nmap -sI <ip>
El escaneo UDP es similar al escaneo TCP SYN, pero en lugar de enviar paquetes TCP SYN, envía paquetes UDP. Si el puerto está abierto, el objetivo responde con un paquete UDP. Si el puerto está cerrado, el objetivo no responde. Si el puerto está filtrado, el objetivo no responde.
nmap -sU <ip>
Nmap tiene múltiples opciones para realizar escaneos como ya hemos visto. Si no especificamos nada, por defecto, nmap realiza un escaneo de puertos TCP SYN.
El escaneo básico que podríamos hacer es el siguiente:
nmap <ip>
Si quisiéramos realizar un escaneo de puertos UDP, podríamos hacerlo de la siguiente forma:
nmap -sU <ip>
Podemos filtrar los puertos que queremos escanear de la siguiente forma:
nmap -p 1-100 <ip>
También podríamos especificar puertos individuales:
nmap -p 1,2,3,4,5 <ip>
O una combinación de ambos:
nmap -p 1-100,200,300,400 <ip>
Nmap se conecta a un puerto y obtiene la información que nos devuelve. Esta información se conoce como banner. Podemos obtener esta información de la siguiente forma:
nmap -sV <ip>
Nmap tiene una gran cantidad de scripts que podemos utilizar para realizar escaneos más avanzados. Podemos ver la lista de scripts disponibles con el siguiente comando:
nmap --script-help
Para ejecutar un script en concreto, podemos hacerlo de la siguiente forma:
nmap --script=<script> <ip>
Algunos scripts tienen argumentos que podemos pasarle. Para ver los argumentos de un script, podemos hacerlo de la siguiente forma:
nmap --script-help=<script>
Cuando tenemos claro como funciona un script, podemos ejecutarlo de la siguiente forma:
nmap --script=<script> --script-args=<argumentos> <ip>
Por último, nmap tiene una opción para ejecutar automáticamente los scripts recomendados a cada puerto automáticamente (Es muy lento y poco efectivo si la máquina tiene muchos puertos abiertos). Podemos hacerlo de la siguiente forma:
nmap -sC <ip>
Estos serían algunos ejemplos de escaneos más comunes que podríamos realizar:
nmap -v -p- <ip> # Escaneo de todos los puertos de una máquina
Cuando sabemos todos los puertos abiertos de una máquina, podemos realizar un escaneo de servicios para obtener información más detallada de cada uno de ellos. Es importante no volver a escanear todos los puertos y centrarnos solo en los abiertos para ahorrar tiempo y recursos. Podemos hacerlo de la siguiente forma:
nmap -v -sV -p <puertos abiertos> <ip> # Escaneo de servicios de los puertos abiertos de una máquina
SNMP o Simple Network Mangement Protocol es un protocolo usado para monitorizar los dispositivos de una red ( por ejemplo, routers, switches, impresoras, IoTs…). Las versiones 1,2 y 2c son bastante inseguras y transmiten la información en texto plano. Estos problemas se solucionaron con la versión 3. En cualquiera de los casos teniendo credenciales se puede lsitar información muy valiosa de un sistema.
Nmap tiene varios scripts para enumerar información sobre este protocolo.
Lo primero que tendremos que identificar es el protocolo y puerto. Por defecto, opera en el 161 UDP. Con NMAP podríamos lanzar un barrido a todos los puertos UDP con el siguiente comando:
nmap -v -p- -sU <Objetivo>
Una vez identificado el protocolo y puerto, especificamos a nmap el puesto concreto sobre el que operar:
nmap -p161 -sU <Objetivo>
Ahora toca utilizar los scripts de nmap. Podríamos dejarlo en modo automático con el parámetro -sC pero no es el método más eficaz dado que muchos de estos script requieren parámetros.
Bash nos ofrece una serie de utilidades para buscar información en ficheros de texto. En este apartado vamos a ver algunas de ellas.
El comando find
nos permite buscar ficheros en un directorio y sus subdirectorios. Para realizar una búsqueda simple, podemos hacerlo de la siguiente forma:
find / -name fichero
En este caso, la búsqueda se realiza en el directorio raíz del sistema. El parámetro -name
indica el nombre del fichero que queremos buscar. Si queremos buscar un fichero que contenga una cadena de caracteres, podemos usar el parámetro -iname
:
find / -iname fichero
Para buscar directorios, podemos usar el parámetro -type d
:
find / -type d -iname directorio
Si queremos buscar ficheros que contengan una cadena de caracteres, podemos usar el parámetro -exec grep
:
find / -type f -iname fichero -exec grep "cadena" {} \;
La utilidad locate
nos permite buscar ficheros en el sistema. Para ello, utiliza una base de datos que se actualiza periódicamente. Para buscar un fichero, podemos hacerlo de la siguiente forma:
locate fichero
Para que la base de datos se actualice, podemos usar el comando updatedb
:
updatedb
Esta utilidad es más rápida que find
, pero no siempre encuentra los ficheros que buscamos ya que se basa en una base de datos que no siempre está actualizada.
La utilidad which
nos permite buscar la localización de un comando en el sistema. Para buscar un comando, podemos hacerlo de la siguiente forma:
which comando
Por ejemplo, imaginemos que queremos saber donde esta instalado el comando ls
. Podemos hacerlo de la siguiente forma:
which ls
Esto nos devolverá la ruta donde se encuentra el comando ls
:
/usr/bin/ls # Habitualmente, el comando ls se encuentra en esta ruta
El comando grep
nos permite buscar una cadena de caracteres en un fichero de texto. Para buscar una cadena de caracteres, podemos hacerlo de la siguiente forma:
grep "cadena" fichero
Si queremos buscar una cadena de caracteres en todos los ficheros de un directorio, podemos usar el parámetro -r
:
grep -r "cadena" directorio
Si queremos buscar una cadena de caracteres en todos los ficheros de un directorio y sus subdirectorios, podemos usar el parámetro -R
:
grep -R "cadena" directorio
Podríamos filtar el flujo de salida de otro comando. Por ejemplo, si queremos buscar una cadena de caracteres en los ficheros de un directorio, podemos hacerlo de la siguiente forma:
ls directorio | grep "cadena"
Git es un sistema muy estricto y metódico diseñado para garantizar la integridad de nuestro código a lo largo de infinidad de versiones y cambios generados por múltiples programadores.
Es normal y frecuente en estos procesos equivocarnos, como por ejemplo, escribir el mensaje que no era en un commit, olvidarse de añadir algún archivo en un commit o añadir el que no querías… etc.
Por suerte, para todos ellos hay solución y veremos diferentes comandos que git ofrece para arreglar errores.
Vamos a ir viendo las diferentes opciones agrupadas por comandos:
Esta opción trabaja en conjunto con el comando commit y es una manera práctica de modificar el commit más reciente. Te permite combinar los cambios prepados con el commit anterior en en lugar de crear uno nuevo.
Sin embargo, este comando no se limita a alterar el cambio más reciente, sino que lo reemplaza por completo. Importante tenerlo en cuenta sobre todo en repositorios públicos cuyos commits puedan ser dependencias de otras ramas o herramientas.
Supón que acabas de terminar un commit y quieres modificar su mensaje porque has puesto lo que no debías. Podrías ejecutar esto:
git commit --amend
Tras ejecutarlo se nos mostrará el editor de texto seleccionado en git para editar el menaje del último commit. En esta entrada puedes ver como cambiar el editor de texto que git usará para estas labores.
Podría pasar también, que te hubieras dejado de añadir un archivo al último commit. Es cierto que podrías crear un nuevo commit pero queda más limpio si corriges el anterior. Para ello añadiríamos el o los archivos que nos hubieramos dejado en el anterior commit usando el comando “add”:
git add <fichero>
Y luego, volveríamos a repetir el “ammend”:
git commit --amend
Esto nos permitiría añadir el archivo los archivos omitidos en el commit anterior y corregir el mensaje si fuera necesario.
Que pasaría si hacemos lo contrario que en punto anterior, en vez de añadir un archivo, lo queremos eliminar. Muchas veces por error incluimos en el repositorio un archivo que no queríamos dado que contienen secretos o información importante.
Si solo lo hemos mandado al “stage” area, podríamos quitarlo de ahí con el siguiente comando:
git reset <fichero a eliminar>
En caso de haberlo añadido al “stage area” y haber hecho un commit, podríamos revertir el cambio con el siguiente comando:
git reset --soft HEAD~1 #Revertir el último commit
git reset <archivo a eliminar> #Resturar el archivo del commiteado por error
rm <archivo a eliminar> #Eliminar el archivo del repositorio
git commit #Hacer el commit
Esto revertirá el último commit, eliminará el archivo y añadira un nuevo commit en su lugar.
Podría pasar, ya en el peor de los casos, que hubieramos hecho muchos cambios mal y quisieramos volver a un estado anterior. Primero podríamos consultar el historial de commits con el comando “log” o “reflog” y ver la referencia del commit al que queremos volver:
git log
git reflog
Con la referencia del commit al que queremos volver, podemos revertir el commit con el siguiente comando:
git reset HEAD@{Referencia}
Para volver al último commit sin tener que consultar el historial, podemos usar el comando “reset” con el parámetro “–hard”:
git reset --hard HEAD
En este apartado veremos los errores más comunes que pueden ocurrir en las ramas.
Es frecuente que con las prisas escribamos el nombre de una rama con un nombre equivocado. Aquí la solución es simple, dentro del comando branch esta el parámetro “-m” que nos permite cambiar el nombre de la rama:
git branch -m nombre-rama-equivocada nombre-rama-correcta
Podríamos hacer sin querer un commit en la rama principal, por ejemplo, main cuando nuestro sistema de organización es hacer ramas distintas para cada característica nueva que se desarrolla o trabajar primero en develop y luego integrar los cambios en main.
En varios pasos podríamos crear una rama con todos los cambios que acabamos de generar y luego, en el siguiente paso, resetear la rama principal al commit anterior:
git branch nombre-rama-nueva-con-los-cambios #Creamos una rama con los cambios
git reset HEAD~ --hard #Reseteamos la rama principal al commit anterior
git checkout nombre-rama-nueva-con-los-cambios #Cambiamos a la rama nueva
En el último paso, nos cambiaríamos a la rama nueva para seguir trabajando con los cambios habiendo dejado limpia la rama principal.
En este apartado veremos como eliminar los secretos de un repositorio local o remoto. Es muy frecuente sin querer introducir tokens o contraseñas en un repositorio. Aunque los borremos posteriormente, estos, se mantendrán en el historial de git.
Podemos borrar un archivo de toda la historia con el siguiente comando:
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch ARCHIVO-SENSIBLE" \
--prune-empty --tag-name-filter cat -- --all
git push --force --verbose --dry-run
git push --force
Dependiendo del sistema operativo en el que nos encontremos Git utilizara un editor u otro para los mensajes de commit en el terminal. En algunos por defecto es nano, en otros vim, gedit… etc. Con el siguiente comando puedes elegir el que más se adapte a tus gustos y necesidades.
En mi caso, prefiero Vim y usaría el siguiente comando:
git config --global core.editor "vim"
También serviría para Neovim usando:
git config --global core.editor "nvim"
Si quisieras usar nano sería tan fácil como usar el siguiente:
git config --global core.editor "nano"
Si es la primera vez que utilizamos git en un sistema, al hacer un commit, es obligatorio que este quere registrado con el nombre y email de un usuario. Se puede configurar con los siguientes comandos:
git config --global user.name "John Doe"
git config --global user.email "johndoe@example.com"
Git tiene un sistema de funcionamiento muy estricto para evitar conflictos y ayudarnos a mantener nuestro bien versionado. Para ello, solo a un proceso realziar cambios a la vez para mantener la integridad de la información.
Cuando realizamos cualquier tarea en git, un commit, push, pull… este genera un archivo llamado “index.lock” y lo guarda dentro de la carpeta “.git” en la raiz del repositorio.
Este archivo bloquea el repositorio ante cualquier otro acceso o proceso simult�neo que quiera realizar cambios. En algunos casos, poco frecuentes, puede pasar que una acción o tarea nunca termine ( por fallo del SO u otros) y el repositorio se quede bloqueado.
Si tenemos claro lo que estamos haciendo, podríamos borrar simplemente este archivo con el comando:
rm .git/index.lock
Así de simple conseguiríamos quitar el bloqueo de git pero atención que no tengamos otro proceso ejecutando alguna tarea sobre git o podríamos corromper datos del repositorio.
Puede ocurrir, por ejemplo usando github, que tengamos varias cuentas y no podamos usar la misma clave ssh que tenemos en el sistema para todos los repositorios. Este problema surge a raiz de que github no permite tener la misma clave ssh repetida en diferentes cuentas.
En nuestro local podríamos generar otra pareja de claves que tuvieran otro nombre y luego en nuestro repositorio de git, especificar manualmente que clave queremos que use nuestro repositorio.
git config --local core.sshCommand "ssh -i ~/.ssh/id_rsa_personal -F /dev/null"
Las variables en python son contenedores para almacenar valores. Estos valores pueden ser de cualquier tipo, como números, cadenas, listas, diccionarios, etc.
Se pueden declarar variables usando el operador de asignación =
. Por ejemplo:
x = 5
y = "Hello, World!"
Python es un lenguaje de tipado dinámico, lo que significa que no necesitas declarar el tipo de variable. Cuando se crea una variable, se le asigna un tipo de datos. El tipo de datos puede cambiar durante la ejecución del programa.
Un nombre de variable debe comenzar con una letra o el carácter de subrayado _
. No puede comenzar con un número. Un nombre de variable solo puede contener caracteres alfanuméricos y guiones bajos (A-z, 0-9 y _). Los nombres de variables son sensibles a mayúsculas y minúsculas, por lo que myvar
y myVar
son dos variables diferentes.
Python permite asignar valores a múltiples variables en una sola línea.
x, y, z = "Orange", "Banana", "Cherry"
print(x)
print(y)
print(z)
También puede asignar el mismo valor a múltiples variables en una sola línea.
x = y = z = "Orange"
Las cadenas de texto o strings
en Python se pueden declarar usando comillas simples o dobles.
x = "Hello World"
Las cadenas de texto pueden ser de una línea o de varias líneas. Para declarar una cadena de texto de varias líneas, se debe usar tres comillas simples o dobles.
x = """Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua."""
Podemos utilizar diferentes operadores para realizar operaciones con cadenas de texto.
Por ejemplo, si queremos concatenar dos cadenas de texto, podemos usar el operador +
.
x = "Hello"
y = "World"
z = x + y
print(z) # Output: HelloWorld
Las cadenas en python son un conjunto de caracteres. Por lo tanto, cada carácter tiene un índice asociado. El primer carácter tiene el índice 0, el segundo carácter tiene el índice 1, etc.
Por ejemplo, si queremos acceder al primer carácter de una cadena de texto, podemos usar el índice 0.
x = "Hello World"
print(x[0]) # Output: H
Si queremos acceder al último carácter de una cadena de texto, podemos usar el índice -1.
x = "Hello World"
print(x[-1]) # Output: d
Se trataría exactamente igual que una lista. Si queremos acceder a un rango de caracteres, podemos usar el operador :
.
x = "Hello World"
print(x[2:5]) # Output: llo
Las cadenas de texto en Python son inmutables. Esto significa que no podemos cambiar los caracteres de una cadena de texto una vez que se ha creado.
x = "Hello World"
x[0] = "J" # Error
Si queremos cambiar un carácter de una cadena de texto, tenemos que crear una nueva cadena de texto.
x = "Hello World"
y = "J" + x[1:]
print(y) # Output: Jello World
Las cadenas de texto en Python tienen muchos métodos que podemos utilizar.
El método lower()
devuelve la cadena de texto en minúsculas.
x = "Hello World"
print(x.lower()) # Output: hello world
El método upper()
devuelve la cadena de texto en mayúsculas.
x = "Hello World"
print(x.upper()) # Output: HELLO WORLD
El método capitalize()
devuelve la cadena de texto con la primera letra en mayúsculas.
x = "hello world"
print(x.capitalize()) # Output: Hello world
El método len()
devuelve la longitud de la cadena de texto.
x = "Hello World"
print(len(x)) # Output: 11
El método replace()
devuelve una nueva cadena de texto donde se han reemplazado todas las apariciones de una cadena de texto por otra.
x = "Hello World"
print(x.replace("H", "J")) # Output: Jello World
El método split()
devuelve una lista donde la cadena de texto se ha dividido en subcadenas en cada aparición del carácter especificado.
x = "Hello World"
print(x.split(" ")) # Output: ['Hello', 'World']
Tenemos tres métodos para quitar espacios en blanco de una cadena de texto.
Rstrip quita los espacios en blanco del final de la cadena de texto.
x = "Hello World "
print(x.rstrip()) # Output: Hello World
Lstrip quita los espacios en blanco del principio de la cadena de texto.
x = " Hello World"
print(x.lstrip()) # Output: Hello World
Strip quita los espacios en blanco del principio y del final de la cadena de texto.
x = " Hello World "
print(x.strip()) # Output: Hello World
Podemos utilizar el método format()
para formatear cadenas de texto.
El método format()
toma los argumentos pasados, los formatea y los inserta en la cadena de texto donde se encuentran los corchetes {}
.
x = "Hello {}"
print(x.format("World")) # Output: Hello World
Podemos pasar múltiples argumentos al método format()
.
x = "Hello {}, you are {} years old"
print(x.format("John", 36)) # Output: Hello John, you are 36 years old
También podemos utilizar índices para especificar el orden en el que se insertan los argumentos.
x = "Hello {1}, you are {0} years old"
print(x.format(36, "John")) # Output: Hello John, you are 36 years old
O nombres.
x = "Hello {name}, you are {age} years old"
print(x.format(age = 36, name = "John")) # Output: Hello John, you are 36 years old
Por último, también podríamos utilizar el operador %
para formatear cadenas de texto.
x = "Hello %s, you are %d years old"
print(x % ("John", 36)) # Output: Hello John, you are 36 years old
Podemos utilizar los métodos startswith()
y endswith()
para comprobar si una cadena de texto empieza o termina con una subcadena de texto.
x = "Hello World"
print(x.startswith("Hello")) # Output: True
print(x.endswith("World")) # Output: True
Podemos utilizar el operador in
para comprobar si una cadena de texto contiene una subcadena de texto.
x = "Hello World"
print("Hello" in x) # Output: True
print("Goodbye" in x) # Output: False
Podemos utilizar el método find()
para encontrar la posición de la primera aparición de una subcadena de texto dentro de una cadena de texto.
x = "Hello World"
print(x.find("World")) # Output: 6
En Python, podemos realizar operaciones numéricas de forma muy sencilla. Para ello, utilizamos los operadores aritméticos:
Operador | Descripción |
---|---|
+ | Suma |
- | Resta |
* | Multiplicación |
/ | División |
% | Módulo |
** | Potencia |
// | División entera |
La operación más sencilla es la suma. Para ello, utilizamos el operador +
:
x = 5
y = 3
print(x + y) # Output: 8
Para realizar una resta, utilizamos el operador -
:
x = 5
y = 3
print(x - y) # Output: 2
En el caso de la multiplicación, utilizamos el operador *
:
x = 5
y = 3
print(x * y) # Output: 15
En una división, utilizamos el operador /
:
x = 5
y = 3
print(x / y) # Output: 1.6666666666666667
El módulo es el resto de una división. Para calcularlo, utilizamos el operador %
:
x = 5
y = 3
print(x % y) # Output: 2
La división entera es la división que devuelve el resultado sin decimales. Para calcularla, utilizamos el operador //
:
x = 5
y = 3
print(x // y) # Output: 1
Podemos calcular la potencia de un número utilizando el operador **
:
x = 5
y = 3
print(x ** y) # Output: 125
Otra forma de calcular la potencia es utilizando la función pow()
:
x = 5
y = 3
print(pow(x, y)) # Output: 125
En el caso opuesto a la potencia, podemos calcular la raíz cuadrada de un número utilizando la función sqrt()
:
x = 25
y = 5
print(sqrt(x)) # Output: 5.0
Sin especificar credenciales:
mysql # Sin login
Con usuario y contraseña (la password la pide por pantalla):
mysql -u root -p # Con usuario y contraseña
Mostrar todas:
show databases;
Crear base de datos:
create database <base de datos>;
Borrar base de datos:
drop database <base de datos>;
Consultar tablas:
show tables;
Describir los atributos de una tabla:
DESCRIBE <tabla>;
Crear tabla:
mysql> CREATE TABLE <tabla>(
id CLAVE NOT NULL AUTO_INCREMENT,
nombre CHAR(30) NOT NULL,
edad INTEGER(30),
salario INTEGER(30),
PRIMARY KEY (id) );
Insertar datos:
INSERT INTO <tabla> (nombre, edad, salario) VALUES
("Pedro", 24, 21000),
("Maria", 26, 24000),
("Juan", 28, 25000),
("Luis", 35, 28000),
("Monica", 42, 30000),
("Rosa", 43, 25000),
("Susana", 45, 39000);
Actualizar datos:
UPDATE <tabla> SET nombre = "Pedro" WHERE id = 1;
Obtener datos:
SELECT * FROM <tabla>;
Borrar datos:
DELETE FROM <tabla> WHERE id = 1;
Para salir del cli interactivo de mysql se puede usar la opción quit
o exit
.
exit;
Las consultas en SQL son una forma de acceder a la base de datos.
Estas consultas tienen diferentes cláusulas:
FROM
: Selecciona la tabla.WHERE
: Selecciona los registros que cumplan con una condición.ORDER BY
: Ordena los registros por un atributo.GROUP BY
: Agrupa los registros por un atributo.LIMIT
: Limita el número de registros a devolver.HAVING
: Selecciona los registros que cumplan con una condición. Opera sobre los registros agrupados.La siguiente consulta devolvería todos los registros de la tabla tabla
.
SELECT * FROM tabla;
Ejemplo where:
La siguiente consulta devolvería el registro con el id 1 de la tabla tabla
.
SELECT * FROM tabla WHERE id = 1;
Ejemplo order by:
Por defecto, ORDER BY
devolvería todos los registros la tabla tabla
ordenados por el id
de forma ascendente (sin especificar nada), también podría usarte el DESC
para ordenarlas de forma ascendent
SELECT * FROM tabla ORDER BY id DESC;
Ejemplo group by:
La siguiente consulta devolvería todos los registros agrupados por el campo nombre
.
SELECT * FROM tabla GROUP BY nombre;
Ejemplo limit:
La siguiente consulta devolvería los primeros 5 registros de la tabla tabla
.
SELECT * FROM tabla LIMIT 5;
Ejemplo having:
SELECT * FROM tabla GROUP BY nombre HAVING COUNT(*) > 1;
Los operadores nos permiten establecer condiciones en las consultar, modificarlas o agruparlos.
Existen diferentes operadores de comparación que nos permiten comparar dos valores.
Operador | Función |
---|---|
< | Menor que |
> | Mayor que |
<> | Distinto de |
= | Igual a |
<= | Menor o igual que |
>= | Mayor o igual que |
IN | Dentro de (filas de tabla) |
NOT IN | Fuera de (filas de tabla) |
BETWEEN | Entre (valores numéricos) |
LIKE | Contiene (valor de cadena) |
Algunos ejemplos:
SELECT * FROM table WHERE precio > 10; /*Selecciona todos los registros con precio mayor a 10*/
SELECT * FROM table WHERE precio < 10; /*Selecciona todos los registros con precio menor a 10*/
SELECT * FROM table WHERE precio <> 10; /*Selecciona todos los registros con precio distinto a 10*/
SELECT * FROM table WHERE precio = 10; /*Selecciona todos los registros con precio igual a 10*/
SELECT * FROM table WHERE precio <= 10; /*Selecciona todos los registros con precio menor o igual a 10*/
SELECT * FROM table WHERE precio >= 10; /*Selecciona todos los registros con precio mayor o igual a 10*/
SELECT * FROM table WHERE precio IN (10, 20, 30); /*Selecciona todos los registros con precio 10, 20 o 30*/
SELECT * FROM table WHERE precio NOT IN (10, 20, 30); /*Selecciona todos los registros con precio distinto a 10, 20 o 30*/
SELECT * FROM table WHERE precio BETWEEN 10 AND 20; /*Selecciona todos los registros con precio entre 10 y 20*/
Concretamente el comando LIKE
nos permite buscar dentro de una cadena de texto. Este comando se aplica a los campos de tipo varchar
o char
. Se apoya en los siguientes operadores:
Operador | Función |
---|---|
% | Comodín, representa cualquier cadena de 0 o más caracteres |
_ | Representa a un único carácter cualquiera |
Combinando el LIKE
con el %
o con el _
podemos buscar por palabras completas o parciales.
Algunos ejemplos con %
:
SELECT * FROM tabla WHERE nombre LIKE '%Pedro%'; /*Todas las filas que contengan la palabra Pedro*/
SELECT * FROM tabla WHERE nombre LIKE 'Pedro%'; /*Todas las filas que comiencen por Pedro*/
SELECT * FROM tabla WHERE nombre LIKE '%Pedro'; /*Todas las filas que terminen por Pedro*/
Algunos ejemplos con _
:
Nos permiten establecer condiciones en las consultas. También nos permite agrupar varias consultas y condiciones a su vez.
Los operadores lógicos son:
Operador | Función |
---|---|
AND | Y |
OR | O |
NOT | No |
Algunos ejemplos:
SELECT * FROM tabla WHERE nombre LIKE '%Pedro%' AND precio < 10;
SELECT * FROM tabla WHERE nombre LIKE '%Pedro%' OR precio < 10;
SELECT * FROM tabla WHERE precio < 10 AND nombre LIKE '%Pedro%';
SELECT * FROM tabla WHERE nombre LIKE '%Pedro%' AND precio < 10 OR precio > 20;
SELECT * FROM tabla WHERE nombre LIKE '%Pedro%' AND (precio < 10 OR precio > 20);
SELECT * FROM tabla WHERE nombre LIKE '%Pedro%' OR (precio < 10 AND precio > 20);
SELECT * FROM tabla WHERE (nombre LIKE '%Pedro%' AND precio < 10) OR precio > 20;
Nos permiten agrupar registros por un campo.
Operador | Función |
---|---|
COUNT() | Cuenta los registros que cumplan con la condición |
SUM() | Suma los valores de un campo |
MAX() | Devuelve el valor máximo de un campo |
MIN() | Devuelve el valor mínimo de un campo |
AVG() | Devuelve la media de un campo |
Algunos ejemplos:
SELECT COUNT(*) FROM tabla;
SELECT SUM(precio) FROM tabla;
SELECT MAX(precio) FROM tabla;
SELECT MIN(precio) FROM tabla;
SELECT AVG(precio) FROM tabla;
A veces, para realizar alguna operación de consulta, necesitamos los datos devueltos por otra consulta.
Una subconsulta, que no es más que una sentencia SELECT dentro de otra SELECT.
Las subconsultas son aquellas sentencias SELECT que forman parte de una cláusula WHERE de una sentencia SELECT anterior. Una subconsulta consistirá en incluir una declaración SELECT como parte de una cláusula WHERE.
Un ejemplo de subconsultas:
SELECT apellido FROM empleados WHERE oficio = (SELECT oficio FROM empleados WHERE apellido ='gil');
Podemos insertar, modificar y borrar datos de las tablas a través de consultas de SQL. Podemos usar los comandos INSERT, UPDATE y DELETE.
Importante, antes de usar cualquiera de estos comandos, debemos indicar la base de datos sobre la que queremos aplicar la operación con el comando USE
.
Ejemplo:
USE base_de_datos;
Para insertar datos, podemos usar el comando INSERT.
INSERT INTO <tabla> (<atributos>) VALUES (<valores>);
Por ejemplo:
INSERT INTO persona (nombre, edad, salario) VALUES ('Juan', 30, 1000);
Las inserciones se podrían realizar con subconsultas, como por ejemplo, con un select anidado:
INSERT INTO persona (nombre, edad, salario) VALUES ( 'Juan', 30, (SELECT salario FROM persona WHERE nombre = 'Juan') );
Para insertar datos con un select, podemos usar el comando INSERT con la siguiente sintaxis:
INSERT INTO <tabla> (<atributos>) SELECT <valores> FROM <tabla>;
Esto nos permitiría insertar datos de una tabla en otra, por ejemplo:
INSERT INTO persona (nombre, edad, salario) SELECT nombre, edad, salario FROM persona;
También podríamos combinar datos procedentes del SELECT con datos estáticos introducidos manualmente:
INSERT INTO persona (nombre, edad, salario) SELECT nombre, edad, 1600, FROM persona WHERE nombre = 'Juan';
O hacer operaciones con datos procedentes del SELECT, como por ejemplo, duplicarle el salario a Juan:
INSERT INTO persona (nombre, edad, salario) SELECT nombre, edad, salario * 2 FROM persona WHERE nombre = 'Juan';
Para modificar datos, podemos usar el comando UPDATE. Cabe destacar el uso del WHERE para especificar que valores queremos actualizar, si no lo especificamos, se actualizarán todos los datos de la tabla. La sintaxis es la siguiente:
UPDATE <tabla> SET <atributos> = <valores> WHERE <condiciones>;
Por ejemplo, para modificar un atributo de una tabla:
UPDATE persona SET nombre = 'Pedro' WHERE id = 1;
También podríamos actualizar múltiples valores de una fila separándolos por comas:
UPDATE persona SET nombre = 'Pedro', edad = 30 WHERE id = 1;
Para borrar datos, podemos usar el comando DELETE.
DELETE FROM <tabla> WHERE <condiciones>;
Por ejemplo:
DELETE FROM persona WHERE id = 1;
Estas se utilizan para manipular y filtrar los datos que se devuelven en una consulta. Se pueden usar en las cláusulas WHERE
y HAVING
pero lo más común es usarlas en las cláusulas SELECT
.
Esta consulta de ejemplo nos permitiría obtener el salario redondeado de todos los empleados y modificando la salida (sin alterar el registro de la basa de datos):
SELECT nombre, ROUND(salario) FROM empleados;
Nos permiten realizar operaciones aritméticas sobre los valores de los campos.
Operador | Función |
---|---|
ABS() | Valor absoluto |
ROUND(n,d) | Redondea el valor “n” con con el número de decimales especificados en “d” |
FLOOR() | Redondea hacia abajo |
CEIL() | Redondea hacia arriba |
SQRT() | Raíz cuadrada |
POW() | Potencia, ejemplo POW(x,y) el valor x elevado a el exponente y |
Nos permiten manipular textos de las consultas.
Operador | Función |
---|---|
CONCAT() | Concatena dos o más cadenas de texto |
SUBSTRING(c,m,n) | Devuelve una sub-cadena obtenida de la cadena “c”, a partir de la posición “m” y tomando “n” caracteres. |
LENGTH() | Devuelve la longitud de una cadena de texto |
UCASE() | Convierte una cadena de texto a mayúsculas |
LCASE() | Convierte una cadena de texto a minúsculas |
REPLACE(c,b,s) | Reemplaza en la cadena “c” el valor buscado en “b” por el valor indicado en “s” |
TRIM() | Elimina los espacios en blanco de una cadena de texto |
REPLICATE(c,n) | Repite la cadena “c” tantas veces como indique la variable “n” |
Nos permiten manipular fechas de las consultas.
Operador | Función |
---|---|
DATE() | Convierte una fecha a una cadena de texto |
DATE_FORMAT() | Convierte una fecha a un formato de cadena de texto |
NOW() | Devuelve la fecha actual |
YEAR() | Devuelve el año de una fecha |
MONTH() | Devuelve el mes de una fecha |
QUARTER() | Devuelve el trimestre del año de una fecha |
DAY() | Devuelve el día de una fecha |
HOUR() | Devuelve la hora de una fecha |
MINUTE() | Devuelve los minutos de una fecha |
SECOND() | Devuelve los segundos de una fecha |
Hay momentos en los que una consulta necesita columnas de varias tablas. En este caso podremos definir en el FROM
todas las tablas que queremos usar.
SELECT * FROM tabla1, tabla2, tabla3;
Si lo hacemos de esta forma, nos devolverá todos los registros de las tablas pero al no haberlas relacionado nos mostrará los registros de cada unos de ellas combinados entre sí. Estos datos no tendrían coherencia.
Veamos un ejemplo, vamos a usar las tablas empleados
y la tabla departamentos
:
EMPLEADOS
id | nombre | apellidos | departamento_id |
---|---|---|---|
1 | Pepe | Pérez | 1 |
2 | Ana | Muñoz | 1 |
3 | Juan | González | 2 |
4 | María | Rubio | 2 |
DEPARTAMENTOS
id | nombre |
---|---|
1 | Ingeniería |
2 | Finanzas |
Si quisiéramos mostrar los empleados junto con los nombres de su departamento, podríamos hacerlo de la siguiente forma:
SELECT empleados.nombre, empleados.apellidos, departamentos.nombre FROM empleados, departamentos WHERE empleados.departamento_id = departamentos.id;
Cuando seleccionamos datos de varias tablas tenemos que difinir en el WHERE
la relación entre ellas. En este caso, el id
de la tabla empleados
y el departamento_id
de la tabla departamentos
porque es la única forma de mostrar el nombre del departamento al que pertenece cada empleado.
El JOIN
es una forma de relacionar dos tablas y tiene varios comandos. La consulta anterior es un ejemplo de INNER JOIN
realizado con un WHERE
. Existen varios tipos de JOIN
en función de la información que quieras obtener de dos tablas relacionadas.
El más común es el INNER JOIN
que nos permite obtener los registros de una tabla que están relacionados con otra.
Aunque son los más comunes, hay más tipos de los que se muestran en la representación visual. De momento nos centraremos en los más comunes:
El INNER JOIN
nos permite seleccionar los registros que tengan coincidencias en ambas tablas, es decir, que estén relacionados. Para ello, en el FROM
seleccionamos la tabla de la que queremos obtener los registros y en el JOIN
seleccionamos la tabla con la que queremos relacionar. Por último, tendríamos que definir en el ON
del JOIN
la relación entre ambas tablas de la misma forma que en el WHERE
de la consulta anterior.
Ejemplo:
SELECT empleados.nombre, empleados.apellidos, departamentos.nombre FROM empleados JOIN departamentos ON empleados.departamento_id = departamentos.id;
El LEFT JOIN
nos permite seleccionar todos los registros de la tabla de la izquierda junto con los registros de la tabla de la derecha que tengan coincidencias.
Ejemplo de uso:
SELECT empleados.nombre, empleados.apellidos, departamentos.nombre FROM empleados LEFT JOIN departamentos ON empleados.departamento_id = departamentos.id;
El RIGHT JOIN
nos permite seleccionar todos los registros de la tabla de la derecha junto con los registros de la tabla de la izquierda que tengan coincidencias.
Ejemplo de uso:
SELECT empleados.nombre, empleados.apellidos, departamentos.nombre FROM empleados RIGHT JOIN departamentos ON empleados.departamento_id = departamentos.id;
El FULL JOIN
nos permite seleccionar todos los registros de la tabla de la izquierda junto con los registros de la tabla de la derecha aunque no tengan coincidencias.
Ejemplo de uso:
SELECT empleados.nombre, empleados.apellidos, departamentos.nombre FROM empleados FULL JOIN departamentos ON empleados.departamento_id = departamentos.id;
Un disparador o trigger es un tipo de procedimiento almacenado que se ejecuta cuando se intentan alterar los datos de una tabla o vista. La diferencia con los procedimientos almacenados es que los disparadores:
Su función es ejecutarse cuando ocurre algún evento en una tabla. Estos eventos pueden ser insertar, modificar o borrar datos ( INSERT, UPDATE, DELETE ).
Antes de empezar, hay que detallar algunos variables que se usarán en el siguiente ejemplo:
name
): Nombre del disparador.time
)[ AFTER o BEFORE ]: se puede especificar cuando se ejecuta el disparador. Antes o después de la ejecución de la consulta.evento
)[ INSERT, UPDATE o DELETE ]: se puede especificar el evento en el que se desea que se ejecute el disparador.nombre_tabla
): se puede especificar el nombre de la tabla en la que se desea que se ejecute el disparador.orden
opcional) [ FOLLOWS o PRECEDES ]: se puede especificar el orden en el que se desea que se ejecute el disparador.cuerpo
): se puede especificar el cuerpo del disparador.La sintaxis de un disparador es la siguiente:
CREATE TRIGGER <name> <time> <evento>
ON <nombre_tabla> FOR EACH ROW
BEGIN
<cuerpo>
END;
Por ejemplo:
CREATE TRIGGER tr_insert_persona AFTER INSERT ON persona FOR EACH ROW
BEGIN
<cuerpo>
END;
El cuerpo del disparador es el código que se ejecuta cuando se lanza el disparador. Dentro de un disparador nos podemos referir a los datos que se están insertando, modificando o borrando. Esto nos permite evitar que se borren o modifiquen datos indeseados; aplicar condiciones como por ejemplo, que haya stock suficiente para un producto.. etc.
El cuerpo del disparador se puede definir en varias partes. En cada parte se puede definir una sentencia SQL.
Por ejemplo, controlar la edad de una persona para evitar que se inserten menores de edad:
CREATE TRIGGER tr_insert_persona BEFORE UPDATE ON persona FOR EACH ROW
BEGIN
IF NEW.edad < 18 THEN
UPDATE persona SET edad = 18 WHERE id = NEW.id;
END IF;
END;
En el cuerpo podemos definir variables, condiciones, sentencias SQL, etc.
Por ejemplo, un disparador en el que consultamos y almacenamos en variables datos de otras tablas:
CREATE TRIGGER tr_insert_persona AFTER INSERT ON persona FOR EACH ROW
BEGIN
DECLARE nombre_mascota VARCHAR(50);
SET nombre_mascota = (SELECT nombre FROM mascotas WHERE id = NEW.id);
INSERT INTO persona_mascota (id, nombre, edad, fecha_nacimiento, ) VALUES (NEW.id, nombre_mascota, NEW.edad, NEW.fecha_nacimiento);
END;
#TODO: Diferencias entre NEW y OLD…
Con el fin de mejorar la eficiencia y reutilizar las consultas SQL, se ha desarrollado una serie de procedimientos y funciones que permiten realizar operaciones de forma más eficiente.
Estos objetos se almacena en la base de datos y se pueden utilizar en las consultas SQL.
Sus principales diferencias son:
Los procedimientos son rutinas o subprogramas compuestos por un conjunto nombrado de sentencias SQL agrupados lógicamente para realizar una tarea específica, que se guardan en la base de datos y que se ejecutan como una unidad cuando son invocados por su nombre. Es decir, nos permiten agrupar un conjuntos de sentencias para lanzarlas en bloque.
El procedimiento consta de las siguientes partes:
Sintaxis básicas de procedimientos almacenados:
CREATE PROCEDURE <nombre_procedimiento>
(
<parametro_entrada> <tipo>
<parametro_entrada> <tipo>
...
)
...
Los parámetros de entrada pueden ser de los siguientes tipos:
IN
: De entradaOUT
: De salidaINOUT
: De entrada y salidaUn ejemplo completo sería el siguiente:
CREATE PROCEDURE procedimiento_ejemplo (IN nombre VARCHAR(50), OUT edad INT)
BEGIN
SELECT edad INTO edad FROM usuarios WHERE nombre = nombre ;
END
Las funciones son rutinas o subprogramas compuestos por un conjunto. Estas siempre tiene un valor de retorno, el cuál, cuyo tipo depende de la declaración de la función con la sintaxis RETURNS <tipo>
y luego en el cuerpo de la función se devuelve con la instrucción RETURN <valor>
.
Por ejemplo, la siguiente función devuelve el nombre de un usuario. Primero hacemos un select que asigna el resultado a una variable y luego hacemos que la función devuelva el valor de la variable.
CREATE FUNCTION nombre_usuario (id_usuario INT) RETURNS VARCHAR(50)
BEGIN
DECLARE nombre_obtenido VARCHAR(50);
SELECT nombre INTO nombre_obtenido FROM usuarios WHERE id_usuario = id_usuario;
RETURN nombre_obtenido;
END
Un cursor es una consulta declarada que provoca que el servidor, cuando se realiza la operación de abrir cursor, cargue en memoria los resultados de la consulta en una tabla interna. Teniendo abierto el cursor, es posible, mediante una sentencia FETCH, leer una a una las filas correspondientes al cursor y, por tanto, correspondientes a la consulta definida. Los cursores deben declararse después de las variables locales.Los cursores nos permiten tras una consulta SQL, recuperar los resultados de la misma y poder trabajar con ellos de uno en uno.
Para hacer uso de un cursor, tendremos que:
CLOSE
)Los cursores se usan dentro de procedimientos y funciones
Este cursor nos permite leer uno a uno los resultados de una consulta para trabajar con los datos. Cabe destacar la necesidad de abrir y cerrar el cursor (OPEN
y CLOSE
).
Podemos leer los valores de un cursor con el comando FETCH
:
DECLARE <variable> <tipo>
DECLARE <nombre_cursor> CURSOR FOR <consulta>;
OPEN <nombre_cursor>;
FETCH <nombre_cursor> INTO <variable>;
CLOSE <nombre_cursor>;
Al leer los valores de un cursor, podemos repetir el proceso de leer los valores de un cursor. Para ello, podemos usar el comando FETCH
repetidamente:
DECLARE <nombre_cursor> CURSOR FOR <consulta>;
OPEN <nombre_cursor>;
FETCH <nombre_cursor> INTO <variable>;
FETCH <nombre_cursor> INTO <variable>;
FETCH <nombre_cursor> INTO <variable>;
FETCH <nombre_cursor> INTO <variable>;
CLOSE <nombre_cursor>;
Pero si quisieramos leer todos los valores de un cursor, lo normal es hacer un bucle para no estar repitiendo la misma operación tantas veces como filas tenga el cursor.
Para leer valores hasta un momento específico, podemos usar el comando REPEAT UNTIL
. Vamos a suponer en este caso que queremos sumar el precio de los 5 primeros resultados de un SELECT
.
DECLARE total INT DEFAULT 0;
DECLARE precio INT DEFAULT 0;
DECLARE contador INT DEFAULT 1;
DECLARE cursor1 CURSOR FOR SELECT precio FROM productos;
OPEN cursor1;
REPEAT
FETCH cursor1 INTO precio;
SET total = total + precio;
SET contador = contador + 1;
UNTIL contador > 5;
END REPEAT;
CLOSE cursor1;
Para leer todos los valores de un cursor, podemos usar el comando LOOP
. Vamos a suponer en este caso que queremos sumar el precio de todos los resultados de un SELECT
. Para ello, podemos usar el comando LOOP
. Aún así, tenemos que ayudar a nuestro programa para que sepa cuando termina de leer los valores. Para ello, utilizamos un HANDLER
para saber cuando no hay valores y utilizar una variable auxiliar para decirle al programa que salga del LOOP
.
Cuando el handler detecta que no hay valores, el HANDLER
añade a la variable auxiliar el valor 1
. Como el bucle LOOP
al principio de la sentencia comprueba si la variable auxiliar
es igual a uno, saldría del programa gracias a la intrucción LEAVE <nombre del bucle>
.
DECLARE total INT DEFAULT 0;
DECLARE precio INT DEFAULT 0;
DECLARE auxiliar INT DEFAULT 0;
DECLARE cursor1 CURSOR FOR SELECT precio FROM productos;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET auxiliar = 1;
OPEN cursor1;
bucle:LOOP
FETCH cursor1 INTO precio;
IF auxiliar = 1 THEN
LEAVE bucle;
END IF;
SET total = total + precio;
END LOOP:bucle;
CLOSE cursor1;
Exportar e importar bases de datos es un proceso crítico y totalmente necesario para proteger nuestros datos.
Estas exportaciones se puede hacer con clientes como mysql workbench, phpmyadmin, etc. En la práctica, suele haber incompatibilidades entre algunas vesiones de los clientes con el servidor de bases de datos. Personalmente, siempre recomiendo utilizar la utilidad de terminal que lleva el propio servidor para minimizar la posibilidad de errores.
A continuación, una lista de como exportar e importar bases de datos sql clasificados por tecnología:
Podemos exportar con el siguiente comando:
mysqldump -u USER -p DATABASE > salida.sql #La contraseña nos la pedirá interactivamente
Ejemplo real:
mysqldump -u root -p database1 > database1.sql
Exportar múltiples bases de datos:
mysqldump -u root -p --databases database1 database2 database3 > databases.sql
Exportar tablas específicas:
mysqldump -u root -p --databases database1 --tables table1 table2 table3 > tables.sql
Podemos importar con el siguiente comando:
mysql -u USER -p DATABASE < salida.sql
Podemos crear usuarios para que accedan a la base de datos. Para ello, utilizamos el comando CREATE USER
:
CREATE USER 'usuario'@'localhost' IDENTIFIED BY 'password';
El comando anterior crea un usuario llamado usuario
con contraseña password
que solo puede acceder desde el servidor localhost
.
Podemos modificar la contraseña de un usuario con el comando SET PASSWORD
:
SET PASSWORD FOR 'usuario'@'localhost' = PASSWORD('nueva_password');
También podríamos haber utilizado el comando ALTER USER
:
ALTER USER 'usuario'@'localhost' IDENTIFIED BY 'nueva_password';
Podemos modificar el nombre de un usuario con el comando RENAME USER
:
RENAME USER 'usuario'@'localhost' TO 'nuevo_usuario'@'localhost';
Podemos eliminar un usuario con el comando DROP USER
:
DROP USER 'usuario'@'localhost';
Podemos eliminar todos los usuarios con el comando DROP ALL USERS
:
DROP ALL USERS;
Si en algún momento queremos bloquear a un usuario, podemos hacerlo modificando sus permisos y aplicando el atributo ACCOUNT LOCK
:
ALTER USER 'usuario'@'localhost' ACCOUNT LOCK;
Podemos desbloquear a un usuario con el comando UNLOCK ACCOUNT
:
ALTER USER 'usuario'@'localhost' UNLOCK ACCOUNT;
Podemos crear permisos para que los usuarios puedan acceder a la base de datos. Para ello, utilizamos el comando GRANT
:
GRANT ALL PRIVILEGES ON *.* TO 'usuario'@'localhost';
Pero ojo, porque este comando da todos los permisos al usuario. Si queremos darle permisos específicos, podemos hacerlo de la siguiente manera:
GRANT SELECT, INSERT, UPDATE, DELETE ON <base de datos>.* TO 'usuario'@'localhost';
También podríamos dar permisos específicos a una tabla en concreto:
GRANT SELECT, INSERT, UPDATE, DELETE ON base_de_datos.tabla TO 'usuario'@'localhost';
Por último, podemos dar permisos para que el usuario afectado pueda gestionar permisos a otros usuarios:
GRANT SELECT, INSERT ON mysql.* TO 'usuario'@'localhost' WITH GRANT OPTION;
Podríamos dar permisos a un usuario para que pueda acceder a una vista, sería similar a dar permisos a una tabla:
GRANT SELECT ON base_de_datos.vista TO 'usuario'@'localhost';
Podemos consultar los permisos de un usuario con el comando SHOW GRANTS
:
SHOW GRANTS FOR 'usuario'@'localhost';
Podríamos consultar los permisos de las tablas desde la “table_priv” de la base de datos “mysql”:
SELECT * FROM mysql.table_priv WHERE User = 'usuario' AND Host = 'localhost';
Para eliminar permisos, utilizamos el comando REVOKE
. En mysql, podemos eliminar todos los permisos de un usuario con el siguiente comando:
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'usuario'@'localhost';
También podemos eliminar permisos específicos:
REVOKE SELECT, INSERT, UPDATE, DELETE ON <base de datos>.* FROM 'usuario'@'localhost';
Los roles son una forma de agrupar usuarios y darles un conjunto de permisos. Sería una especie de plantilla de permisos que se puede aplicar a un usuario.
Podemos crear roles para que accedan a la base de datos. Para ello, utilizamos el comando CREATE ROLE
:
CREATE ROLE 'rol';
Asignamos permisos a un rol con el comando GRANT
:
GRANT SELECT, INSERT, UPDATE, DELETE ON 'tabla' TO 'rol';
Podemos asignar un rol a un usuario con el comando GRANT
:
GRANT 'rol' TO 'usuario'@'localhost';
También podríamos usar el comando set:
SET ROLE 'rol' TO 'usuario'@'localhost';
Por último, lo podríamos asignar por defecto al usuario:
SET DEFAULT ROLE 'rol' TO 'usuario'@'localhost';
Podemos consultar los roles de un usuario con el comando SHOW GRANTS
:
SHOW GRANTS FOR 'usuario'@'localhost';
Podemos modificar el nombre de un rol con el comando RENAME ROLE
:
RENAME ROLE 'rol' TO 'nuevo_rol';
Podemos eliminar un rol con el comando DROP ROLE
:
DROP ROLE 'rol';
Las variables son valores que se pueden modificar en tiempo de ejecución. Podemos consultar el valor de una variable con el comando SHOW VARIABLES
:
SHOW VARIABLES;
Podemos modificar el valor de una variable con el comando SET
:
SET @variable = valor;
También podemos modificar el valor de una variable global con el comando SET GLOBAL
:
SET GLOBAL @variable = valor;
Existen varias herramientas que nos ayudan a trabajar con redes:
# Ping - comprueba la conexión con un hosts y si está acttivo
ping -c 4 google.com # Con el -c 4 el número de veces que se ejecuta el ping
# Traceroute - identifica la ruta que se ha recorrido para llegar a un host
traceroute google.com
# Netstat - muestra los puertos abiertos en el sistema
netstat -l
# ARP - muestran la tabla ARP del sistema que actúa como una cache.
arp -a # Muestra la relación entre direcciones IP y direcciones MAC
Con el comando ip
podemos alterar la configuración de una interfaz de red. Para ello hay múltiples opciones de este comando que debemos conocer previamente:
Listar las interfaces de red, información y estado:
ip addr
# O su versión corta
ip a
Ver la configuración de una interfaz de red:
ip addr show ens33
# O su versión corta
ip a s ens33
Habilitar o deshabilitar una interfaz de red:
#Habilitar
ip link set ens33 up
#Deshabilitar
ip link set ens33 down
El estado de una interfaz de red lo podríamos ver tras el state
de la configuración de la interfaz marcado como UP
o DOWN
.:
ip a s ens33
ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:cc:35:ff brd ff:ff:ff:ff:ff:ff
inet 172.17.71.94/20 brd 172.17.79.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fecc:35ff/64 scope link
valid_lft forever preferred_lft forever
Para asignar una dirección IP a una interfaz de red debemos usar el comando ip addr add
:
ip addr add <IP>/<MASCARA> dev <INTERFACE>
#Por ejemplo:
ip addr add 192.168.1.56/255.255.255.0 dev ens33
#o
ip addr add 192.168.1.56/24 dev ens33
También podríamos añadir la dirección de broadcast:
ip addr add <IP>/<MASCARA> broadcast <BROADCAST> dev <INTERFACE>
# O su versión corta
ip a a <IP>/<MASC
Para comprobar si una interfaz de red tiene una puerta de enlace activa podemos usar el comando ip route show
:
ip route show
También podemos obtener la información de enrutamiento a una IP particular usando:
ip route get <IP>
Antes de comenzar, vamos a parar el servicio ‘network-manager’ para que no interfiera con nuestra configuración. Este servicio es el encargado de gestionar las redes. Esto lo podemos hacer con el comando:
systemctl stop network-manager
Para configurar una red en Ubuntu Server tendremos que crear un fichero llamado 01-netcfg.yaml
en la carpeta /etc/netplan
.
El fichero especifica por cada red si queremos utilizar DHCP, dirección IP manual (en caso de no usar DHCP), gateays y servidores DNS nameservers
.
Ejemplo (obviar las líneas que comienzan por #
ya que son comentarios para explicar el fichero):
network:
version: 2
renderer: NetworkManager
# Se especifiaca cada una de las redes que queremos configurar
ethernets:
# Ejemplo de red con DHCP que recibirá la dirección IP automáticamente
ens33:
dhcp4: yes
dhcp6: yes
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
# Ejemplo de una red con IP manual
ens38:
dhcp4: no
dhcp6: no
addresses: [192.168.1.120/24]
# Se especifica la puerta de enlace. IP del router.
gateway4: 192.168.1.1
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
Una vez terminado de editar el fichero, tenemos que aplicar los cambios con el comando:
netplan apply
Finalmente, volveríamos a arrancar el servicio ‘network-manager’ para que los cambios surjan efecto:
systemctl start network-manager
GNU GRUB es un cargador de arranque múltiple, desarrollado por el proyecto GNU que nos permite elegir qué Sistema Operativo arrancar de los instalados. Se usa principalmente en sistemas operativos GNU/Linux.
Una configuración que siempre me gusta ajustar es el tiempo de espera, normalmente unos 5 segundos.
Para configuraciones con múltiples sistemas operativos esta bien que haya cierto margen para elegir el que queramos pero, al menos en mi caso, usando solo linux no veo necesidad de demorar el tiempo de arranque.
Esta configuración se puede editar en el fichero:
/etc/default/grub
Cambiando el parámetro “GRUB_TIMEOUT” por el valor que deseemos:
GRUB_DEFAULT=0
GRUB_TIMEOUT=5 # <-- CAMBIAR POR EL VALOR QUE QUIERAS
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX=""
En mi caso, lo pongo a 0 para agilizar lo máximo posible.
El servicio de NFS es un servicio de red que nos permite compartir archivos entre distintos equipos. Este funciona de forma similar a una carpeta compartida en Windows. Esta pensado para funcionar como servidor o cliente. El servidor comparte una carpeta con uno o más clientes y los clientes acceden a los archivos de la carpeta ( montando las carpetas en su sistema de ficheros ).
Esta guía se parte por un lado en la configuración de un servidor NFS y por otro en la configuración de un cliente NFS.
Para funcionar NFS sobre el servidor debemos instalar los siguientes paquetes:
apt install nfs-kernel-server nfs-common
Con esto ya podemos para a la configuración.
Primero tenemos que crear la carpeta que vamos a compartir (en el caso de que ya la tengas puedes omitir este paso):
mkdir <carpeta_compartida>
Luego tenemos que asignar un propietario especial a esta carpeta.
Este usuario nobody
y grupo nogroup
se utilizan para que los clientes remotos puedan acceder a los archivos de la carpeta compartida.
chown nobody:nogroup <carpeta_compartida>
Para configurar el servidor NFS, debemos modificar el archivo de configuración /etc/exports
. Al final del fichero de configuración debemos añadir la siguiente línea:
/ruta/carpeta/compartida <origenes permitidos>(<opciones>)
# Ejemplo, los orígenes son todos los clientes, especificado con *.
/home/usuario/carpeta_compartida *(rw,sync,no_subtree_check,no_root_squash)
# Podíamos especificar múltiples clientes con distintas opciones por cada uno de ellos
/ruta/carpeta/compartida 192.168.1.56(rw,sync) 192.168.1.68(rw,sync)
Podemos especificar los origenes permitidos al servidor de las siguientes maneras:
192.168.1.0/24
permitiría todas las IPs de la red desde 192.168.1.0
hasta 192.168.1.255
.*
para indicar que todos los clientes están permitidos y tambien se puede combinar con nombres de dominios. Por ejemplo, *.example.com
permitiría todos los clientes que tengan con subdominio de example.com
.@grupo_de_red
.Veamos las diferentes opciones que podemos configurar por cada origen o grupo de orígenes:
rw
para lectura y escritura y ro
para solo lectura.sync
para sincronizar los archivos y async
para no sincronizar los archivos.no_subtree_check
para que no se compruebe el subdirectorio y no_root_squash
para que no se comparta el root.Una vez configuradas las carpetas a compartir tendríamos que reiniciar el servicio para que se aplique la nueva configuración.
systemctl restart nfs-kernel-server
Para permitir conexiones remotas en el firewall debemos permitir conexiones al puerto 2049
y 111
que utiliza el servicio NFS. Con ufw
en ubuntu se puede hacer de la siguiente manera:
# Permitir orígenes específicos en el firewall
ufw allow from <origen> to any port nfs
# Permitir todos los orígenes en el firewall
ufw allow from any to any port nfs
Para funcionar NFS como cliente debemos instalar el siguiente paquete:
apt install nfs-common
Debemos espesificar la carpeta donde vamos a montar los archivos remotos. Esto se puede hacer con el comando mount
:
mount <IP del servidor>:/ruta/carpeta/remota /ruta/carpeta/local
Una vez montada la carpeta podemos listar todos los puntos de montaje del servidor con el siguiente comando:
showmount -e <IP del servidor>
También podríamos listar los montajes locales con el comando:
df -h
Por último, podríamos desmontar un punto de montaje con el comando:
umount /ruta/carpeta/local
Podemos montar automáticamente la carpeta remota en el arranque añadiendo una línea de configuración en el archivo /etc/fstab
:
La línea tendría el siguiente formato:
<IP del servidor>:<ruta/carpeta/remota> /ruta/carpeta/local nfs <opciones> 0 0
# Por ejemplo:
192.168.1.56:/home/user/compartida /home/cliente/compartida nfs rw,nofail,noatime,nolock,intr,tcp,actimeo=1800 0 0
Ahora podríamos reiniciar el servidor y comprobar que la carpeta se ha montado automáticamente. A veces tarda unos segundos/minutos en realizar el montaje.
En lo personal me encanta el terminal y en lo profesional lo veo indispensable para ciertas tareas.
Adaptarlo a nuestras necesidades y potenciar sus utilidades de caja me parece vital si pasas muchas horas delante de una interfaz de comandos.
En esta guía explico como configurar zsh a mis gustos personales, (en este vídeo tenéis todo el proceso más detallado):
Con el siguiente comando podéis instalar todos los requisitos necesarios para instalar zsh:
apt install curl zsh
Ahora que tenemos instalado zsh podemos instalar el plugin de ohmyzsh:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
e voila!
Antigen es un plugin de ohmyzsh que nos permite configurar zsh con una interfaz más sencilla, funcionando como gestor de plugins y temas.
Podemos descargarlo con el siguiente comando:
curl -L git.io/antigen > antigen.zsh
Es importante que tengamos localizado el archivo antigen.zsh
, personalmente lo suelo guardar dentro de la carpeta .oh-my-zsh
por limpieza.
Para configurar antigen debemos añadir el siguiente comando en el fichero .zshrc
:
source <ubicación del archivo antigen.zsh>
Para configurar un plugin podemos usar el comando ‘antigen bundle’ y para seleccionar un tema se usa el comando ‘antigen theme’.
Por ejemplo:
# Definición de plugins
antigen bundle git
antigen bundle git-prompt
# Definición de un tema
antigen theme robbyrussell
Para finalizar la configuración de antigen debemos añadir el siguiente comando en el fichero .zshrc
:
antigen apply
La principal ventaja que nos aporta antigen es que deja un fichero de configuración mucho más limpio y, además, nos instala automaticamente los plugins y temas que hemos defino.
Las próxima vez que hagamos source .zshrc
antigen se encargará de gestionarlo todo.
Así quedaría el fichero .zshrc
completo:
source ~/.oh-my-zsh/antigen.zsh
# Load the oh-my-zsh's library.
antigen use oh-my-zsh
# Bundles from the default repo (robbyrussell's oh-my-zsh).
antigen bundle git
antigen bundle git-prompt
antigen bundle z
antigen bundle pip
antigen bundle kubectl
antigen bundle git-prompt
antigen bundle vi-mode
antigen bundle docker
antigen bundle docker-compose
# Syntax highlighting bundle.
antigen bundle zsh-users/zsh-syntax-highlighting
antigen bundle zsh-users/zsh-autosuggestions
# Load the theme.
antigen theme bureau
# Tell Antigen that you're done.
antigen apply
Recuerda que todas las configuraciones que hagas en zsh se guardarán en el archivo .zshrc
. Aquí podemos editar el tema, los plugins, añadir alias.. etc.
Se puede editar el tema de zsh en el parámentro ZSH_THEME
: del fichero .zshrc
. Por ejemplo:
ZSH_THEME="robbyrussell"
Así se cambia al tema por defecto de ohmyzsh.
Para añadir un plugin a zsh, debemos añadirlo en la sección plugins
del fichero .zshrc
separados por espacios. Por ejemplo:
plugins=(git sudo docker z )
Recuerda que cada vez que modifiques el fichero .zshrc tendras que cargar de nuevo la configuración con el comando:
source ~/.zshrc
Estas funcionalidades no vienen por defecto pero si podemos intalar los plugins para activarlos posteriormente. Aquí los enlaces a sus repositorios: autocomplete highlight
Los podríamos instalar respectivamente con los siguientes comandos:
#Instalar autocompletado
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
#Instalar resaltado de color
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
Los podríamos activar añadiéndolos en el parámetro de plugins en el fichero .zshrc
:
plugins=(zsh-syntax-highlighting zsh-autosuggestions)
Hoy en día cualquier aplicación la podemos ejecutar en nuestro navegador web. Pero, ¿por qué tener aplicaciones sueltas si podemos usarlo para acceder a nuestro sistema operativo completo?. Es verdad que existen soluciones como Microsoft 365, Horizon y demás, las cuales están enfocadas principalmente en entornos corporativos windows y la mayoría requieren de clientes específicos para poder usarlos.
El objetivo de esta guía es explicar como ejecutar una distribución linux en tu navegador. Bueno, siendo sinceros, esto tiene un poco de truco. Efectivamente vamos a conseguir tener acceso a una distribución linux en el navegador, pero realmente no lo esta ejecutando el mismo.
Normalmente este tipo de sistemas funcionan en linux gracias a un proyecto llamado Apache Guacamole. Esta es una herramienta de conexión remota que permite hacer de cliente para conectarse a un sistema a través de RDP o VNC.
Este es el esquema de lo que vamos a hacer:
El objetivo es que nuestro contenedor lance un servicio de VNC o RDP que no esté expuesto y por otra parte un servidor de Apache Guacamole que exponha un servidor web al que podemos conectarnos con el navegador web. Desde esta página podremos gestionar entornos remotos y por debajo otro servicio hace de cliente contra estos entornos remotos.
Veremos dos formas de conseguirlo, la primera totalmente automática y la segunda haremos de forma manual para desglosar los pasos.
De la mano de las imágenes de docker de linuxserver.io podemos ejecutar contenedores de linux con todo lo hablado anteriormente, en diferentes distribuciones y con diferentes gestores de ventanas.
Ofrecen las principales distribuciones de linux, como ubuntu, alpine, fedora, arch en combinación con los gestores de ventanas más populares XFCE, KDE, i3, Openbox, IceWM… etc.
Podéis consultar el listado completo en linuxserver.io. Este es un breve resumen del tag que tendrías que usar en la imagen de tu contenedor en función del sabor que prefieras:
Tag | Description |
---|---|
latest | XFCE Alpine |
ubuntu-xfce | XFCE Ubuntu |
fedora-xfce | XFCE Fedora |
arch-xfce | XFCE Arch |
alpine-kde | KDE Alpine |
ubuntu-kde | KDE Ubuntu |
fedora-kde | KDE Fedora |
arch-kde | KDE Arch |
Para ejecutar un contenedor de esta forma, simplemente ejecutamos el comando con la configuración de los diferentes parámetros. Importante entender los parámetros opciones, concretamente, el montaje del socket. Este nos permitirá usar docker dentro del contenedor pero pondrá en riesgo la seguridad de nuestro entorno en caso de ser vulnerado.
Sobre la seguridad en contenedores hablo más extendidamente en esta video entrada Seguridad en contenedores.
Comando de ejemplo (en la sección de Parameters de DockerHub se explica cada uno de ellos):
docker run -d \
--name=webtop \
--security-opt seccomp=unconfined `#optional` \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Europe/London \
-e SUBFOLDER=/ `#optional` \
-e KEYBOARD=en-us-qwerty `#optional` \
-p 3000:3000 \
-v /path/to/data:/config \
-v /var/run/docker.sock:/var/run/docker.sock `#optional` \
--device /dev/dri:/dev/dri `#optional` \
--shm-size="1gb" `#optional` \
--restart unless-stopped \
lscr.io/linuxserver/webtop
Personalmente, si no necesitas usar docker dentro de este contenedor ni montar ningún archivo lo ejecutaría así (para potenciar su seguridad, poner el teclado en español y la hora local):
docker run -d \
--name=pabpereza.dev \
--security-opt seccomp=unconfined `# Activar solo si no te funciona correctamente` \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Europe/Madrid `# Hora local` \
-e KEYBOARD=es-es-qwerty `# Teclado en castellano` \
-e AUTO_LOGIN=true `# Poner a false para quitar el autologin (recuerda cambiar la pass)` \
-p 3000:3000 \
-v /var/run/docker.sock:/var/run/docker.sock `#Borrar si no queremos usar docker dentro del contenedor` \
--shm-size="1gb" `#Previene a los navegadores modernos fallar por límites de memoria` \
--restart unless-stopped \
lscr.io/linuxserver/webtop `# Despues de webtop con : podríamos añadir el tag para usar la distribución que queramos por defecto Alpine con XFCE`
Ya lo tendríamos funcionando. También podemos mejorar el rendimiento siguiendo la guía de Hardware acceleration (en la página de DockerHub) aunque solo funciona en el contenedor de ubuntu.
Más que aconsejable cambiar la contraseña al usuario del contenedor para que nadie pueda acceder a él directamente o poner un proxy previo con autenticación de algún tipo. Además, puedes desactivar el autologin
(Variable de entorno AUTO_LOGIN en el comando de docker run).
Esto lo podemos hacer con el comando (en un terminal dentro del contenedor):
sudo passwd <usuario>
Seguidamente se nos pedirá la nueva contraseña para el usuario (si os pide la anterior por defecto es abc como el usuario). Las próximas veces que entréis al contenedor a través del navegador os pedirá la contraseña para conectarse.
Para ciertos escenarios es recomendable desactivar el swap o incluso obligatorio. Por ejemplo, en el caso de kubernetes si o si se debe desactivar el swap.
En ubuntu 20.04 podemos ver el estado de la memoria swap con el siguiente comando:
sudo swap --show
# También nos serviría el comando free
sudo free -h
Para desactivarlas, podemos usar el comando:
sudo swapoff -a #Esto la desactiva, pero solo de forma temporal
# Luego tendríamos que borrar el archivo de intercambio
sudo rm /swap.img
# Por último, borramos la línea de swap del fichero de configuración de linux fstab para que no se recree cuando el sistema se reinicie
sudo sed -i '/swap/d' /etc/fstab
En este entrada vamos a ver cómo utilizar tmux. Tmux es un multiplexor de terminales que nos permite crear sesiones de terminal, dividirlas en paneles y ventanas, y acceder a ellas desde cualquier terminal.
En la mayoría de distribuciones podemos instalar tmux con el gestor de paquetes de la distribución. Por ejemplo, en Ubuntu:
sudo apt install tmux
En macOS podemos instalarlo con brew:
brew install tmux
Para crear una nueva sesión de tmux, ejecutamos el comando tmux
:
tmux
Si queremos crear una sesión con un nombre, podemos usar el parámetro -s
:
tmux -s my_session
Para listar las sesiones que tenemos abiertas, podemos usar el comando tmux ls
:
tmux ls
Para reanudar una sesión, podemos usar el comando tmux attach
:
tmux attach -t my_session
Para reanudar la última sesión, podemos usar el comando tmux attach
sin parámetros:
tmux attach
Dentro de una sesión, podemos suspenderla con el comando Ctrl+b d
. Esto nos permite volver a la sesión más tarde con el comando tmux attach
.
Página oficial: Oh My Posh
Aquí tenéis también el video tutorial:
El módulo de oh my posh se puede instalar desde la documentación de este enlace: Instalación en windows
Mediante winget (el gestor de paquetes nativo de windows) podemos instalarlo con un solo comando:
winget install JanDeDobbeleer.OhMyPosh -s winget
Una vez instalado, nos queda configurarlo para que arranque cada vez que iniciamos powershell. Para ello,
abrimos el fichero profile
de powershell:
notepad $PROFILE
Una vez abierto notepad, añadimos las siguientes líneas y guardamos:
oh-my-posh init pwsh | Invoke-Expression
Esto permitirá que se arranque solo cada vez que abramos el terminal de powershell.
Podemos instalar fuentes con el comando (Requiere permisos de administrador):
oh-my-posh font install
Esto nos abrirá un prompt interactivo para elegir la fuente que queremos que nos instale.
También podríamos instalarlas desde la página de Nerd Fonts
Una vez instalada la fuente que nos interesa usar, no nos olvidemos de seleccionarla en nuestra aplicación o gestor de terminales favorito.
En el caso de windows terminal: Configuración (Control , ) -> Perfiles -> Valores predeterminados -> Apariencia -> Tipo de Fuente
Este módulo nos permitirá activar el autocompletado de comandos en base a nuestro historial de una forma gráfica y cómoda:
Instalación:
Install-Module -Name PSReadLine -AllowPrerelease -Scope CurrentUser -Force -SkipPublisherCheck
Además de la instalación tendremos que añadir al nuestro script ubicado $PROFILE
, por defecto, ubicado en ~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1
:
Set-PSReadLineOption -PredictionSource History
Set-PSReadLineOption -PredictionViewStyle ListView
Set-PSReadLineOption -EditMode Windows
oh-my-posh init pwsh --config 'https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/material.omp.json' | Invoke-Expression
Este es el resultado final de mi configuración importando remotamente el tema de oh-my-posh a utilizar.