Parse sequence of characters detecting formatting categories
It looks like you're trying to extract information from a string. This is most often done through regexes as they are often faster than your own code while being less complex.
Because as of writing, you have no clear description of your goal, I'm inferring these assumptions from your code:

You want to split every sequence of characters in your string in one of the categories bold, italic and normal, determined by the characters being enclosed in 2, 1 or 0 asterisks.
You want to preserve the order of the original string.

If we disallow asterisks in the normal text, a regex for this is rather simple. I recommend exploring what it does on regex101.
\*\*(?P&lt;bold&gt;.+?)\*\*|\*(?P&lt;italic&gt;.+?)\*|(?P&lt;normal&gt;[^*]+)

Notably, the | character matches on the first match it finds, from left to right, meaning that a match will always try to match bold, then italic, then normal.
In Python, we can iterate over all the matches using re.finditer. For every match, lastgroup will return the name of the captured group, so we can use that in our print statement and also use it to get the actual match that we're interested in.
def fun(text=None):
    if text:
        for match in re.finditer(r'\*\*(?P&lt;bold&gt;.+?)\*\*|'  # match bold lazily
                                 r'\*(?P&lt;italic&gt;.+?)\*|'  # match italics lazily
                                 r'(?P&lt;normal&gt;[^*]+)',  # match normal greedily
                                 text):
            print(f'{match.lastgroup.upper()}: {match.group(match.lastgroup)}')

It should be noted that if the function is called plenty of times and performance is of concern, you should compile the regex once before using it in fun.
The regex used in this example is very primitive because you haven't specified your constraints or even what the function is meant to do. By specifying your question better and explaining how edge cases like ** a ** *** and **ab*cd** should behave, a more complete answer can be given.
Everything below this is in response to your 'philosophical question':

The regex answer [...] has worse performance.

I did some testing using timeit on the original string (multiplied in size anywhere between 0-3000 times) on differing number of runs and found that the regex approach is consistently 1.5 times faster than the original solution as long as the print statements are included.
Because printing it is unlikely to be your actual use-case, I modified both functions to return a tuple of all bold, italic and normal matches. I was surprised to find that your code is generally twice as fast.

The proposed solution is still much harder to understand (if it were not for the comments).

The comments are exactly what make the regex a relatively readable solution compared to yours. By commenting what the individual parts do, someone that needs to use the code can quickly see what the code achieves. The cost is that it may require more time to figure out how the code works.
Note that this extra cost also diminishes as you get more experience. For some, figuring out how your code achieves its goal is more difficult than understanding the regex. Regexes are very powerful, quite popular, and are used in many languages. This means that when one encounters a regex construct they know what to look for and how to read it. Your solution, meanwhile, is based on a few implicit assumptions like &quot;every other string from the split is the thing we want to match&quot;. Someone that reads your code has to discover these assumptions because they're not explicitly written down.

The proposed solution is still much harder to [...] debug.

If nothing else, it's easy to pinpoint where the issue if there is one: in the regex. Given the input, it shouldn't be hard to figure out why the regex is failing if you inspect it using a tool like regex101. It may be difficult to correct the regex, but at least you won't have a scavenger hunt for where your bug is.

Should I just ignore cyclomatic complexity hints in the future?

Avoiding a high cyclomatic complexity mostly comes down to avoiding nesting a little more. This ensures that you have a more linear (and therefore more comprehensible) flow to your code.
The algorithm you used requires quite a bit of nesting which is not readily avoidable. You can, however, make some improvements by using the if as a guard. Using list comprehension, you can also eliminate the final for-if-else loop.
def fun_unnested_order(text=None):
    if not text:
        return None

    res = []
    bold_splits = text.split('**')  # Odd entries are bold, even entries are italicized or normal.
    for i, s in enumerate(bold_splits):
        if i % 2 == 1:  # Store bold
            res.append((s, 'bold'))
        else:
            italic_splits = s.split('*')  # Odd entries are italicized, rest normal.
            # Replace the for-if-else loop with list comprehension
            res += [(e, 'italic') if i % 2 == 1 else (e, 'normal')
                    for i, e in enumerate(italic_splits)]
    return res

This example is 30% slower than your original code using the same timeit setup.

So is it the better code?

It depends on your viewpoint. If performance is of concern, you should go for greybeard's answer as it is around 30% faster than your original code.
If readability is of concern, I would take the regex version any day of the week. While many may consider regexes black magic, it is widely applicable to many problems in most if not all popular languages. Most crucially perhaps, the regex solution doesn't require a complex algorithm, which means it's easier to develop and less error-prone.
Again, in terms of readability, it's about the familiarity of the programmer. If you are unfamiliar with regexes, list comprehensions and/or list slicing, your original version is the option to go for.
In the end, there is no one implementation that wins across all categories. You should familiarize yourself with the different ways to approach the problem and then tackle it in a way you deem the best solution.