AHdark

AHdark Blog

Senior high school student with a deep passion for coding. Driven by a love for problem-solving, I’m diving into algorithms while honing my skills in TypeScript, Rust, and Golang.
telegram
tg_channel
twitter
github

How to Deploy WordPress on Kubernetes

Introduction to Main Tools#

In this section, we will introduce the basic concepts of Kubernetes and Helm, which are essential for understanding how to deploy WordPress on a Kubernetes cluster.

Introduction to Kubernetes#

Kubernetes is an open-source container orchestration platform for automating the deployment, scaling, and management of containerized applications. Kubernetes provides a unified API and tools that make deploying and managing containers simpler and more reliable. It can manage containers running on multiple machines, ensuring high availability, resilience, scalability, and security of container applications.

Kubernetes mainly includes the following core concepts:

  • Pod: The smallest deployable unit in Kubernetes, containing one or more closely related containers.
  • Deployment: Used to manage the number of replicas of Pods and updates.
  • Service: Used to expose the network address of Pods or Deployments both internally and externally within the cluster.
  • Volume: Used to mount persistent data into containers.
  • Namespace: Used for logical isolation and grouping of Kubernetes resources.

Introduction to Helm#

Helm is a package manager for Kubernetes that is used to create, share, and deploy predefined Kubernetes application packages (called charts). Helm abstracts the complexity of Kubernetes by using charts, simplifying the deployment and management of applications. With Helm, you can easily create custom charts and share them with others.

Helm mainly includes the following core concepts:

  • Chart: A predefined template for Kubernetes application packages. This template can include Services, Deployments, Ingress, etc., allowing you to deploy applications without executing multiple kubectl apply -f commands.
  • Release: A specific instance of a Chart installed on Kubernetes.
  • Repository: A location for storing and sharing Charts.

In the following chapters, we will delve deeper into Helm and how to use it to deploy WordPress.

Introduction to KubeSphere#

KubeSphere is a container platform based on Kubernetes that provides a range of tools and services to help users manage and run applications on Kubernetes. KubeSphere simplifies the use of Kubernetes by providing a more user-friendly interface, making it easier for users to deploy, manage, and scale Kubernetes applications.

KubeSphere offers several core concepts, such as Workspace, Project, and Namespace, which are used to organize and manage Kubernetes resources for better application management and resource isolation.

To use KubeSphere, you need to understand the following concepts:

  • Cluster: One of the levels in KubeSphere's multi-tenant system, referring to a Kubernetes Cluster composed of a group of physical or virtual machines. It is the basic management unit in KubeSphere. In KubeSphere, you can add multiple Kubernetes Clusters to different Cluster Groups for management and easily manage the nodes, Pods, and services of the cluster through the KubeSphere interface.
  • Workspace: A Workspace refers to a group of users and Projects that share Kubernetes Cluster resources. In KubeSphere, a Workspace can be seen as a logical virtual organization that can contain multiple Projects and provide resource isolation and management.
  • Project: A Project is a child of a Workspace, consisting of a collection of related Kubernetes resources (such as Pods, Services, ConfigMaps, etc.). In KubeSphere, Projects help users achieve resource isolation and management on shared Kubernetes Clusters. Each Project has its own members and roles, allowing different teams and users to independently develop and deploy applications on the same Kubernetes Cluster while ensuring security and resource utilization.

KubeSphere also provides many useful features and tools, such as DevOps toolchains, application templates, monitoring, and log analysis, which can help users more easily deploy applications, automate testing, CI/CD processes, and operations.

In summary, KubeSphere is a powerful Kubernetes container platform that provides users with convenient and reliable tools and services to help them better manage and run Kubernetes applications.

Reference: What is KubeSphere

Introduction to WordPress Chart#

This chapter will introduce the WordPress Chart, which is a predefined Kubernetes application package that includes WordPress, MariaDB, Memcached, and other necessary components. Using the WordPress Chart allows for quick and easy deployment of a complete WordPress environment, avoiding the complexity of manual configuration and installation.

In this chapter, we will use the WordPress Chart provided by Bitnami: bitnami/wordpress

Features and Usage#

The Chart simplifies the deployment and management of WordPress in various ways.

It provides a predefined application package that includes WordPress, MariaDB, Memcached, and other necessary components, allowing users to easily install and configure them. Additionally, it offers a range of configuration options that enable users to customize WordPress settings easily.

Importantly, it supports automatic scaling, automatic updates, and automatic recovery of containers, ensuring that the WordPress application remains up-to-date and highly available.

Using the Bitnami WordPress Chart requires just a few simple steps:

  1. Add the Bitnami Chart repository to Helm:
  2. Use Helm to install the Bitnami WordPress Chart
  3. Configure WordPress and MariaDB

Here is a code example:

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install wordpress-instance bitnami/wordpress

In this tutorial, we will not deploy directly using Helm but will use the OpenPitrix-based application store provided by KubeSphere.

Installation to KubeSphere#

We will demonstrate how to deploy the Bitnami WordPress Chart to a Kubernetes cluster using Helm and discuss how to choose the appropriate Kubernetes namespace, create a PersistentVolumeClaim, and use an Ingress controller to expose the WordPress service externally.

Specifically, we will cover the following topics:

  1. Adding the Bitnami Charts repository to the KubeSphere application store
  2. Choosing the appropriate Kubernetes namespace
  3. Installing WordPress
  4. Configuring WordPress
  5. Exposing the WordPress service using an Ingress controller

Deploying WordPress Chart#

This chapter will introduce how to deploy WordPress in a Kubernetes cluster using Bitnami's WordPress Chart and KubeSphere.

In this article, you will learn how to add the Bitnami repository in KubeSphere, create a project, and use the helm tool to install the WordPress Chart. Then, we will discuss how to configure Ingress rules to allow external access to the WordPress service. Through this article, you will learn how to deploy WordPress in a Kubernetes cluster and make it accessible externally.

Adding the Bitnami Charts Repository#

To add the Bitnami repository, first log in to the KubeSphere console with a user that has at least self-provider role permissions. Then, go to the workspace, select the cluster and workspace where you want to add the repository. Next, in the left menu, select "Application Management" → "Application Repositories," click the "Add" button, and enter https://charts.bitnami.com/bitnami as the repository address.

Add Bitnami Repository as Application Repository

Add Bitnami Repository as Application Repository

Choosing a Namespace#

In KubeSphere, a namespace is equivalent to a project.

To create a project, go to the "Projects" page in the left menu, then click the "Create Project" button in the upper right corner and enter an appropriate name.

Note that the project name (i.e., namespace name) must be unique across the entire cluster. I recommend associating the name with the workspace to facilitate differentiation and avoid duplication.

Create Project

Create Project

Installing the WordPress Application#

Go to "Application Load" → "Applications" → "Template-based Applications," click the "Create" button in the upper right corner, and select "From Application Template." Next, select the Bitnami repository you just added at the top of the repository selector and search for WordPress.

image

Configuring WordPress#

In one step of the application settings, you need to configure WordPress.

global:
  imageRegistry: ""
  imagePullSecrets: []
  storageClass: ""
kubeVersion: ""
nameOverride: ""
fullnameOverride: ""
commonLabels: {}
commonAnnotations: {}
clusterDomain: cluster.local # Cluster domain name; if you use GKE's Cloud DNS instead of Kube DNS, you need to configure your domain name here
extraDeploy: []
diagnosticMode:
  enabled: false
  command:
    - sleep
  args:
    - infinity
image:
  registry: docker.io
  repository: bitnami/wordpress
  tag: 6.1.1-debian-11-r57 # WordPress image version
  digest: ""
  pullPolicy: IfNotPresent
  pullSecrets: []
  debug: false
wordpressUsername: ahdark # Admin user's username
wordpressPassword: "" # Admin user's password. Leaving it empty will use a random password and add a Secret to your Namespace
existingSecret: "" # You can configure WordPress's username and password through Secret
wordpressEmail: ahdark@outlook.com # Admin user's email address
wordpressFirstName: AH # Admin user's first name
wordpressLastName: Dark # Admin user's last name
wordpressBlogName: AHdark Blog # Blog name
wordpressTablePrefix: wp_ # WordPress database table prefix
wordpressScheme: https # WordPress protocol
wordpressSkipInstall: false # Choose whether to skip WordPress installation. If you are migrating WordPress, you can choose to skip installation.
wordpressExtraConfigContent:
  - # You can configure WordPress's wp-config.php file here; this content will be appended to the end of the wp-config.php file.
    $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
    define( 'WP_HOME', 'https://www.ahdark.blog' );
    define( 'WP_SITEURL', 'http://www.ahdark.blog' );
wordpressConfiguration: "" # This configuration will override the content of the wp-config.php file
existingWordPressConfigurationSecret: "" # You can also configure the wp-config.php file content through Secret
wordpressConfigureCache: false # Choose whether to automatically configure W3 Total Cache's caching feature
wordpressPlugins: none # Choose whether to automatically enable WordPress plugins
apacheConfiguration: "" # This configuration will override the content of Apache's httpd.conf
existingApacheConfigurationConfigMap: "" # You can also configure Apache's httpd.conf content through Config Map
customPostInitScripts: {}
smtpHost: "" # You can configure SMTP services; this will modify the plugin settings. This configuration is not necessary.
smtpPort: ""
smtpUser: ""
smtpPassword: ""
smtpProtocol: ""
smtpExistingSecret: ""
allowEmptyPassword: true
allowOverrideNone: false
overrideDatabaseSettings: false
htaccessPersistenceEnabled: false
customHTAccessCM: ""
command: []
args: []
extraEnvVars: []
extraEnvVarsCM: ""
extraEnvVarsSecret: ""
multisite:
  enable: false
  host: ""
  networkType: subdomain
  enableNipIoRedirect: false
replicaCount: 1
updateStrategy:
  type: RollingUpdate
schedulerName: ""
topologySpreadConstraints: []
priorityClassName: ""
hostAliases:
  - ip: 127.0.0.1
    hostnames:
      - status.localhost
extraVolumes: []
extraVolumeMounts: []
sidecars: []
initContainers: []
podLabels: {}
podAnnotations: {}
podAffinityPreset: ""
podAntiAffinityPreset: soft
nodeAffinityPreset:
  type: ""
  key: ""
  values: []
affinity: {}
nodeSelector: {}
tolerations: []
resources:
  limits: {}
  requests:
    cpu: 100m
    memory: 512Mi
containerPorts:
  http: 8080
  https: 8443
extraContainerPorts: []
podSecurityContext:
  enabled: true
  fsGroup: 1001
  seccompProfile:
    type: RuntimeDefault
containerSecurityContext:
  enabled: true
  runAsUser: 1001
  runAsNonRoot: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
livenessProbe:
  enabled: true
  httpGet:
    path: /wp-admin/install.php
    port: "{{ .Values.wordpressScheme }}"
    scheme: "{{ .Values.wordpressScheme  upper }}"
    httpHeaders: []
  initialDelaySeconds: 120
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 6
  successThreshold: 1
readinessProbe:
  enabled: true
  httpGet:
    path: /wp-login.php
    port: "{{ .Values.wordpressScheme }}"
    scheme: "{{ .Values.wordpressScheme  upper }}"
    httpHeaders: []
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 6
  successThreshold: 1
startupProbe:
  enabled: false
  httpGet:
    path: /wp-login.php
    port: "{{ .Values.wordpressScheme }}"
    scheme: "{{ .Values.wordpressScheme  upper }}"
    httpHeaders: []
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 6
  successThreshold: 1
customLivenessProbe: {}
customReadinessProbe: {}
customStartupProbe: {}
lifecycleHooks: {}
service:
  type: ClusterIP
  ports:
    http: 80
    https: 443
  httpsTargetPort: https
  nodePorts:
    http: ""
    https: ""
  sessionAffinity: None
  sessionAffinityConfig: {}
  clusterIP: ""
  loadBalancerIP: ""
  loadBalancerSourceRanges: []
  externalTrafficPolicy: Cluster
  annotations: {}
  extraPorts: []
ingress:
  enabled: false
  pathType: ImplementationSpecific
  apiVersion: ""
  ingressClassName: ""
  hostname: wordpress.local
  path: /
  annotations: {}
  tls: false
  tlsWwwPrefix: false
  selfSigned: false
  extraHosts: []
  extraPaths: []
  extraTls: []
  secrets: []
  extraRules: []
persistence:
  enabled: true
  storageClass: ""
  accessModes:
    - ReadWriteOnce
  accessMode: ReadWriteOnce
  size: 32Gi # You can set the PVC size for the WordPress application. If you use a disk as the media library storage medium, you need to make this value as large as possible.
  dataSource: {}
  existingClaim: ""
  selector: {}
  annotations: {}
volumePermissions:
  enabled: true
  image:
    registry: docker.io
    repository: bitnami/bitnami-shell
    tag: 11-debian-11-r92
    digest: ""
    pullPolicy: IfNotPresent
    pullSecrets: []
  resources:
    limits: {}
    requests: {}
  containerSecurityContext:
    runAsUser: 0
serviceAccount:
  create: true
  name: ""
  automountServiceAccountToken: true
  annotations: {}
pdb:
  create: false
  minAvailable: 1
  maxUnavailable: ""
autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 11
  targetCPU: 50
  targetMemory: 50
metrics:
  enabled: false
  image:
    registry: docker.io
    repository: bitnami/apache-exporter
    tag: 0.13.0-debian-11-r3
    digest: ""
    pullPolicy: IfNotPresent
    pullSecrets: []
  containerPorts:
    metrics: 9117
  livenessProbe:
    enabled: true
    initialDelaySeconds: 15
    periodSeconds: 10
    timeoutSeconds: 5
    failureThreshold: 3
    successThreshold: 1
  readinessProbe:
    enabled: true
    initialDelaySeconds: 5
    periodSeconds: 10
    timeoutSeconds: 3
    failureThreshold: 3
    successThreshold: 1
  startupProbe:
    enabled: false
    initialDelaySeconds: 10
    periodSeconds: 10
    timeoutSeconds: 1
    failureThreshold: 15
    successThreshold: 1
  customLivenessProbe: {}
  customReadinessProbe: {}
  customStartupProbe: {}
  resources:
    limits: {}
    requests: {}
  service:
    ports:
      metrics: 9150
    annotations:
      prometheus.io/scrape: "true"
      prometheus.io/port: "{{ .Values.metrics.containerPorts.metrics }}"
  serviceMonitor:
    enabled: false
    namespace: ""
    interval: ""
    scrapeTimeout: ""
    labels: {}
    selector: {}
    relabelings: []
    metricRelabelings: []
    honorLabels: false
    jobLabel: ""
networkPolicy:
  enabled: false
  metrics:
    enabled: false
    podSelector: {}
    namespaceSelector: {}
  ingress:
    enabled: false
    podSelector: {}
    namespaceSelector: {}
  ingressRules:
    backendOnlyAccessibleByFrontend: false
    customBackendSelector: {}
    accessOnlyFrom:
      enabled: false
      podSelector: {}
      namespaceSelector: {}
    customRules: {}
  egressRules:
    denyConnectionsToExternal: false
    customRules: {}
mariadb:
  enabled: true
  architecture: standalone
  auth:
    rootPassword: ""
    database: wordpress
    username: wordpress
    password: ""
  primary:
    persistence:
      enabled: true
      storageClass: ""
      accessModes:
        - ReadWriteOnce
      size: 16Gi # You can set the PVC size occupied by the database
externalDatabase:
  host: localhost
  port: 3306
  user: wordpress
  password: wordpress
  database: wordpress
  existingSecret: ""
memcached:
  enabled: false
  auth:
    enabled: false
    username: ""
    password: ""
  service:
    port: 11211
externalCache:
  host: localhost
  port: 11211

It is important to note that I recommend you to forcefully define WP_HOME and WP_SITEURL in wordpressExtraConfigContent, otherwise WordPress will use the dynamic domain name passed in by Ingress, which may affect the use of plugins such as Site Kit and Jetpack.

Exposing WordPress Service Using Ingress#

To allow external access to the WordPress service, we need to configure an Ingress controller in the Kubernetes cluster and create an Ingress rule to route incoming traffic to the WordPress service.

In KubeSphere, you can use the pre-installed Nginx Ingress controller or install other supported Ingress controllers. Here, we will use the Nginx Ingress controller to demonstrate how to configure Ingress rules.

Here are some basic steps:

  1. Deploy the Nginx Ingress controller: You can deploy it by installing the official Nginx Ingress controller in KubeSphere. Once deployed successfully, you should see a Deployment named nginx-ingress-controller and a Service named nginx-ingress-controller in the Kubernetes cluster. If you are using KubeSphere's gateway, skip this step.
  2. Create Ingress rules: Go to Application Load → Application Routing, create an Ingress, and define an Ingress rule to expose the WordPress service externally. In the YAML file, you need to specify the path, host, and backend service for the Ingress rule. Below is an example.
  3. Resolve the corresponding domain name's DNS to the external access IP of the Nginx Ingress.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wordpress-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/proxy-body-size: 80m
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: ImplementationSpecific
            backend:
              service:
                name: wordpress
                port:
                  number: 80

In the above example, we added the nginx.ingress.kubernetes.io/proxy-body-size annotation and set its value to 80m to allow larger request bodies.

After completing the above steps, you should be able to access the WordPress service externally.

Configuring TLS (Optional)#

This section will detail the steps to configure Kubernetes Ingress TLS using Cert Manager.

Method 1: Configure Certificate Using Cert Manager#

  1. Deploy Cert Manager: First, you need to deploy Cert Manager in the Kubernetes cluster. You can find detailed instructions on how to install Cert Manager in the official documentation. Ensure that Cert Manager is correctly deployed and running. For installation details, refer to: Installation - Cert Manager
  2. Create an Issuer or Cluster Issuer: In Kubernetes, an Issuer represents a certificate authority (CA). With Cert Manager, you can easily create an Issuer and automate the certificate issuance process. You can use Cert Manager's predefined Issuer or create your own. Refer to ACME - Cert Manager
  3. Use Cert Manager's Certificate object to create a certificate. You can use the following YAML example as a template.
# Certificate for Cert Manager

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-certificate
spec:
  secretName: tls-secret
  dnsNames:
  - example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer

In the above example, we created a Certificate object named example-certificate and associated it with the Kubernetes Secret object where the certificate and key will be stored. We also specified the hostname to be protected and the Issuer to be used.

Note that we used a ClusterIssuer reference, which means we are using a cluster-wide Issuer. If you created an Issuer in the previous step, you need to make the corresponding changes.

After this, you can check for a Secret named tls-secret in the Secrets of the corresponding Project (Namespace). If it exists and is displayed correctly as a TLS certificate, the certificate has been created.

If you are using a predefined Issuer from Cert Manager (e.g., Let's Encrypt Issuer), the certificate issuance process may take some time to complete. In this case, you can wait for a while and then repeat the above steps to verify whether the certificate has been issued.

Method 2: Directly Upload TLS Certificate#

In the KubeSphere Console, you can create a secret dictionary in the project to store the public and private keys of the TLS certificate by following these steps:

  1. In the project interface, find the "Configuration" option in the left menu and select "Secret Dictionary."
  2. Click the "Create" button, select the "TLS" type, and fill in the name and namespace of the secret dictionary.
  3. In the "Data" tab, enter your TLS certificate public key and private key.
  4. Click the "Create" button to save your secret dictionary.

With these simple steps, you can store your TLS certificate's public and private keys in the secret dictionary for use when deploying applications. This will make your certificate more secure, and only authorized users will be able to view the private key information of the certificate.

Add TLS Certificate

Add TLS Certificate

Binding TLS Certificate to Ingress#

In Kubernetes, you can define an Ingress object using a YAML file and associate it with a Kubernetes Service and backend Pods. In the YAML file, you need to configure TLS using the following fields:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wordpress-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/proxy-body-size: 80m
spec:
  tls:
  - secretName: tls-secret
    hosts:
    - example.com
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: wordpress
            port:
              number: 80

After clicking save, the Ingress will be updated.

Testing and Troubleshooting#

Testing and troubleshooting are essential components of maintaining Kubernetes applications, ensuring that applications run correctly after deployment and that issues can be diagnosed and resolved promptly.

Monitoring Application Metrics and Troubleshooting#

When you use the KubeSphere Console, a visual tool, to deploy and manage your Kubernetes applications, the debugging process is often simpler.

You can use the built-in debugging tools in the KubeSphere Console to quickly diagnose and resolve issues:

  1. View logs in the KubeSphere Console: You can quickly view the logs of Pods and containers by clicking on the Pod's name in the application page of the KubeSphere Console.
  2. Check Kubernetes resources: Use the resource page in the KubeSphere Console to check the status of Kubernetes resources and confirm that they are running normally.
  3. Use the KubeSphere Event Center: The KubeSphere Event Center can record events and alert information for applications, including the startup, shutdown, and error states of Pods and containers. You can use the KubeSphere Event Center to diagnose application issues.

By following these steps, you can debug your Kubernetes applications in the KubeSphere Console to ensure they remain highly available and stable.

Testing the Web Page#

Once the application is deployed, you can view the site content at the specified Ingress location. To ensure the application is running correctly, you need to perform some tests. Here are some methods to test the application:

  1. Access the site: Use a browser to visit the site and ensure that the content displays correctly. Check the layout, images, links, etc., to ensure the site's correctness.
  2. Log in to the WordPress admin panel: Access the control panel of the WordPress site and log in using the admin account. Check the layout, plugins, themes, posts, etc., in the WordPress backend to ensure you can manage the site normally.
  3. Add a post or page: Add a new post or page in the WordPress backend and publish it. Check whether the post or page displays correctly on the frontend.
  4. Test plugins: If you are using WordPress plugins, ensure they work correctly. The method for testing plugins depends on their functionality, such as testing contact forms, social media sharing, etc.
  5. Test custom domain names: If you are using a custom domain name in Ingress, ensure you can access the site correctly through that domain. You can use the nslookup or dig command to test the domain's resolution.

By performing these tests, you can ensure that your application works correctly and promptly discover and resolve any issues that arise.

Summary#

Through this article, you should have learned how to deploy and manage WordPress applications using Kubernetes and KubeSphere, and how to configure Ingress TLS using Cert Manager for secure external access. In this article, we focused on the following content:

  1. Basic knowledge of Kubernetes and Helm: You need to understand the core concepts and basic operations of Kubernetes and Helm to better manage and deploy applications.
  2. Introduction to KubeSphere: KubeSphere is a container platform based on Kubernetes that provides practical tools and features, making application deployment simpler and more reliable.
  3. Deploying WordPress applications: You can deploy and run WordPress applications in Kubernetes using Bitnami's WordPress Chart. Before deploying the application, you need to configure and install the necessary components and plugins and use Ingress to configure external access.
  4. Configuring Ingress TLS: To ensure the security of data transmission, you can configure Ingress TLS using Cert Manager. This involves creating certificates, installing Cert Manager, creating Issuer and Certificate objects, and configuring Ingress TLS.
  5. Testing and troubleshooting: After deploying the application, you need to perform some tests and monitoring to ensure the application runs correctly. When issues arise, you need to quickly diagnose and resolve them to ensure high availability and stability of the application.

With the guidance provided in this article, you should be able to easily deploy and manage WordPress applications while ensuring their security and stability. Additionally, this article provides you with useful techniques and tools to help you better manage and deploy Kubernetes applications.

During the writing of this article, I also completed the deployment of a test WordPress application, which is the ahdark.blog site you see now. This site runs on Kubernetes, using Google Compute Engine as the infrastructure provider. If you are interested in the high availability and convenience of Kubernetes, you can subscribe to the Telegram channel @AHdark_Channel for more real-time updates.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.