Routing Traffic in Kubernetes: Ingress vs. Gateway API Comparison

Kubernetes has become a standard platform for container orchestration, providing powerful capabilities for managing and scaling containerized applications. As the adoption of microservices architecture has grown, so has the need for advanced networking features. Kubernetes offers several networking APIs, including the Ingress API and the Gateway API. In this article, we will explore the differences between these two APIs, and when to use each one.

What is the Ingress API?

The Ingress API is a Kubernetes resource that provides a way to route external traffic to internal services in a Kubernetes cluster. Acting as a reverse proxy and load balancer, Ingress enables the exposure of multiple services via a single IP address and domain name. Ingress rules direct traffic based on the host name, path, or other HTTP headers.

Ingress operates as a Layer 7 (application layer) load balancer. This allows it to route traffic based on the content of the request, making it ideal for routing traffic to HTTP/HTTPS services. It is essential to note that Ingress doesn’t support protocols other than HTTP/HTTPS.

What is the Gateway API?

The Gateway API is a Kubernetes resource that provides a way to configure and manage advanced networking features such as traffic shaping, security, and observability. The Gateway API acts as a Layer 7 (application layer) load balancer and as a Layer 4 (transport layer) load balancer. As a result, it can route traffic based on IP addresses, ports, or protocols, which makes it suitable for routing traffic to non-HTTP/HTTPS services, such as TCP or UDP.

The Gateway API provides several advanced features such as traffic splitting, traffic mirroring, and traffic shifting, which can be used to implement canary deployments, blue-green deployments, and A/B testing.

Differences between Ingress and Gateway

One significant difference between Ingress and Gateway is the layer of the network stack they operate. Ingress is a Layer 7 load balancer that routes traffic based on the content of the request. Gateway, on the other hand, is a Layer 7 and Layer 4 load balancer that can route traffic based on IP addresses, ports, or protocols.

Another difference is the level of complexity they offer. Ingress is a simple API that is easy to set up and use, providing basic routing capabilities that support only HTTP/HTTPS traffic. Gateway, on the other hand, is a more advanced API that offers a wide range of networking features, including traffic shaping, security, and observability. It supports a variety of protocols and is more suitable for complex deployments.

Finally, Ingress requires the use of an Ingress controller, such as Nginx or Traefik, which handles the actual routing of traffic. Gateway, on the other hand, is a standalone resource that does not require an external controller.

When to use Ingress or Gateway

In general, Ingress should be used when routing HTTP/HTTPS traffic, and Gateway should be used when routing traffic to non-HTTP/HTTPS services, such as TCP or UDP. If advanced networking features, such as traffic shaping, security, or observability, are required, Gateway should be used. If only basic routing capabilities are needed, Ingress will suffice.

In some cases, both Ingress and Gateway can be used together. For example, Ingress can be used to route HTTP/HTTPS traffic to services, and Gateway can be used to route non-HTTP/HTTPS traffic.

Conclusion

Kubernetes provides two networking APIs, Ingress and Gateway, to manage and route traffic to containerized applications. Ingress is a Layer 7 load balancer that is suitable for HTTP/HTTPS traffic, while Gateway is a more advanced API that provides a broader range of networking features, including support for protocols beyond HTTP/HTTPS. It’s important to evaluate the specific networking requirements of your application to determine which API is the best fit. By leveraging the right API, you can ensure that your Kubernetes network is optimized for the performance, security, and scalability needs of your application.

Install Traefik in Kubernetes and Deploy Service on non-standard Port

Guide on how to set up Traefik 2.2 with ingressRoutes and whoami service on non-standard entryPoint (e.g. 9001).

Traefik Custom Resource Definitions

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressrouteudps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteUDP
    plural: ingressrouteudps
    singular: ingressrouteudp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsstores.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSStore
    plural: tlsstores
    singular: tlsstore
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: traefikservices.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TraefikService
    plural: traefikservices
    singular: traefikservice
  scope: Namespaced
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - ingressrouteudps
      - tlsoptions
      - tlsstores
    verbs:
      - get
      - list
      - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: default

Deploy Traefik Deployment

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
  name: traefik
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/instance: traefik
      app.kubernetes.io/name: traefik
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: traefik
        app.kubernetes.io/name: traefik
    spec:
      containers:
      - args:
        - --global.checknewversion
        - --entryPoints.traefik.address=:9000/tcp
        - --entryPoints.web.address=:8000/tcp
        - --entryPoints.websecure.address=:8443/tcp
        - --entryPoints.who.address=:9001/tcp
        - --api.dashboard=true
        - --ping=true
        - --providers.kubernetescrd
        - --providers.kubernetesingress
        - --certificatesresolvers.myresolver.acme.tlschallenge=true
        - --certificatesresolvers.myresolver.acme.email=<email@address.com>
        - --certificatesresolvers.myresolver.acme.storage=/data/acme.json
            # Please note that this is the staging Let's Encrypt server.
            # Once you get things working, you should remove that whole line altogether.
        - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
        image: traefik:2.2.8
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /ping
            port: 9000
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 2
        name: traefik
        ports:
        - containerPort: 9000
          name: traefik
          protocol: TCP
        - containerPort: 8000
          name: web
          protocol: TCP
        - containerPort: 8443
          name: websecure
          protocol: TCP
        - containerPort: 9001
          name: who
          protocol: TCP
        readinessProbe:
          failureThreshold: 1
          httpGet:
            path: /ping
            port: 9000
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 2
        resources: {}
        securityContext:
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
          runAsGroup: 65532
          runAsNonRoot: true
          runAsUser: 65532
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /data
          name: data
        - mountPath: /tmp
          name: tmp
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        fsGroup: 65532
      serviceAccount: traefik
      serviceAccountName: traefik
      terminationGracePeriodSeconds: 60
      volumes:
      - emptyDir: {}
        name: data
      - emptyDir: {}
        name: tmp

Notice that “who” entryPoint and containerPort was added.

Deploy Traefik service

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
  name: traefik
  namespace: default
spec:
  externalTrafficPolicy: Cluster
  ports:
  - name: web
    nodePort: 31463
    port: 80
    protocol: TCP
    targetPort: web
  - name: websecure
    nodePort: 31496
    port: 443
    protocol: TCP
    targetPort: websecure
  - name: who
    nodePort: 31498
    port: 9001
    protocol: TCP
    targetPort: who
  selector:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
  sessionAffinity: None
  type: LoadBalancer

Notice again the extra “who” port. Chose a random nodePort.

An extra dashboard service is not required anymore.

Deploy Traefik Dashboard IngressRoute

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`traefik.example.com`)
    kind: Rule
    services:
    - name: api@internal
      kind: TraefikService
    middlewares:
      - name: auth
  tls:
    certResolver: myresolver

---
# Redirect to https
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect-to-https
spec:
  redirectScheme:
    scheme: https
    permanent: true

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: auth
spec:
  basicAuth:
    secret: authsecret

---
apiVersion: v1
kind: Secret
metadata:
  name: authsecret
  namespace: default

data:
  users: |2
    <base64 encoded user|password>

Deploy whoami Deployment, Service and IngressRoute

---
kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: default
  name: whoami
  labels:
    app: whoami

spec:
  replicas: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: containous/whoami
          ports:
            - name: web
              containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: whoami

spec:
  ports:
    - protocol: TCP
      name: web
      port: 80
  selector:
    app: whoami
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoamitls
spec:
  entryPoints: 
    - who
  routes:
  - match: Host(`who.example.com`)
    kind: Rule
    services:
    - name: whoami
      port: 80

Notice that the entryPoint is set to “who”, therefore the service is exposed on port 9001.

Basic firewall-cmd commands

Basic firewall-cmd commands

For reference, these are some of the most used firewall-cmd I use on CentOS 7.

firewall-cmd --list-all-zones

firewall-cmd --zone=internal --change-interface=eth1

firewall-cmd --zone=internal --add-port=80/tcp --permanent
firewall-cmd --zone=internal --add-protocol=vrrp --permanent

firewall-cmd --reload

firewall-cmd --list-ports --zone=internal
firewall-cmd --list-proto --zone=internal

For a complete list got to the official documentation.

Install MongoDB on CentOS 7

How to install MongoDB on CentOS 7

This article explains how to install MongoDB on a CentOS 7 server.

Add MongoDB repository

vim /etc/yum.repos.d/mongodb-org-3.6.repo
[mongodb-org-3.6]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.6/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.6.asc

Update repository list

yum repolist

Install MongoDB

yum install -y mongodb-org

Start MongoDB service and enable after restart

systemctl start mongod
systemctl status mongod
systemctl enable mongod

Show logs

tail /var/log/mongodb/mongod.log

An output of waiting for a connection confirms that MongoDB has started successfully and we can access the database server with the MongoDB Shell:

mongo

To learn how to interact with MongoDB from the shell, you can review the output of the db.help() method which provides a list of methods for the db object.

> db.help()
DB methods:
	db.adminCommand(nameOrDocument) - switches to 'admin' db, and runs command [just calls db.runCommand(...)]
	db.aggregate([pipeline], {options}) - performs a collectionless aggregation on this database; returns a cursor
	db.auth(username, password)
	db.cloneDatabase(fromhost)
	db.commandHelp(name) returns the help for the command
	db.copyDatabase(fromdb, todb, fromhost)
	db.createCollection(name, {size: ..., capped: ..., max: ...})
	db.createView(name, viewOn, [{$operator: {...}}, ...], {viewOptions})
	db.createUser(userDocument)
	db.currentOp() displays currently executing operations in the db
	db.dropDatabase()
	db.eval() - deprecated
	db.fsyncLock() flush data to disk and lock server for backups
	db.fsyncUnlock() unlocks server following a db.fsyncLock()
	db.getCollection(cname) same as db['cname'] or db.cname
	db.getCollectionInfos([filter]) - returns a list that contains the names and options of the db's collections
	db.getCollectionNames()
	db.getLastError() - just returns the err msg string
	db.getLastErrorObj() - return full status object
	db.getLogComponents()
	db.getMongo() get the server connection object
	db.getMongo().setSlaveOk() allow queries on a replication slave server
	db.getName()
	db.getPrevError()
	db.getProfilingLevel() - deprecated
	db.getProfilingStatus() - returns if profiling is on and slow threshold
	db.getReplicationInfo()
	db.getSiblingDB(name) get the db at the same server as this one
	db.getWriteConcern() - returns the write concern used for any operations on this db, inherited from server object if set
	db.hostInfo() get details about the server's host
	db.isMaster() check replica primary status
	db.killOp(opid) kills the current operation in the db
	db.listCommands() lists all the db commands
	db.loadServerScripts() loads all the scripts in db.system.js
	db.logout()
	db.printCollectionStats()
	db.printReplicationInfo()
	db.printShardingStatus()
	db.printSlaveReplicationInfo()
	db.dropUser(username)
	db.repairDatabase()
	db.resetError()
	db.runCommand(cmdObj) run a database command.  if cmdObj is a string, turns it into {cmdObj: 1}
	db.serverStatus()
	db.setLogLevel(level,<component>)
	db.setProfilingLevel(level,slowms) 0=off 1=slow 2=all
	db.setWriteConcern(<write concern doc>) - sets the write concern for writes to the db
	db.unsetWriteConcern(<write concern doc>) - unsets the write concern for writes to the db
	db.setVerboseShell(flag) display extra information in shell output
	db.shutdownServer()
	db.stats()
	db.version() current version of the server
> use admin

switched to db admin

> db.createUser(
 {
    user: "admin",
    pwd: "password",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
  }
 )

Successfully added user: {
	"user" : "admin",
	"roles" : [
		{
			"role" : "userAdminAnyDatabase",
			"db" : "admin"
		}
	]
}
vim /etc/mongod.conf
security:
  authorization: enabled

Restart MongoDB service

systemctl restart mongod

Inside MongoDB shell you now need to authorize:

db.auth('admin', 'password')

Quit the MongoDB shell with the exit command:

exit

MongoDB database files are located at /var/lib/mongo/ .

MongoDB log files are located at /var/log/mongo/ .

How to create a systemd service

How to create a systemd service based on the example of a node.js application

vim /etc/systemd/system/nodeapp.service
[Unit]
Description=NodeJS Web Application
After=network.target

[Service]
ExecStart=/usr/bin/node /var/www/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=nodeapp
User=web
Group=web
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
WorkingDirectory=/var/www/

[Install]
WantedBy=multi-user.target